Nas partes anteriores, criamos um chatbot com ferramentas (Parte 1) e memória de curto prazo dentro de um fio (Parte 2). Agora damos mais dois passos: **memória de longo prazo** (entre fios) para lembrar informações entre conversas, e o padrão **human-in-the-loop** para pausar o grafo e pedir aprovação humana.
⚠️ Este capítulo continua o código das partes anteriores (Parte 1 · Parte 2).
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 completa de LangGraph_**, dividida em quatro capítulos que devem ser lidos 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
Memória de longo prazo, memória entre hilos
A memória é uma função cognitiva que permite às pessoas armazenar, recuperar e utilizar informação para compreender, a partir do seu passado, o seu presente e o seu futuro.
Existem vários tipos de memória de longo prazo que podem ser utilizados em aplicações de IA.
Introdução ao LangGraph Memory Store
LangGraph fornece o LangGraph Memory Store, que é uma forma de guardar e recuperar memória de longo prazo entre diferentes threads. Dessa maneira, em uma conversa, um usuário pode indicar que gosta de algo, e em outra conversa, o chatbot pode recuperar essa informação para gerar uma resposta mais personalizada.
Trata-se de uma classe para armazenamentos persistentes de chave-valor (key-value).
Quando se armazenam objetos na memória, são necessárias três coisas:
- Um
namespacepara o objeto é feito por meio de umatupla - Uma
keyúnica - O
valordo objeto
Vamos ver um exemplo
InputPythonimport uuidfrom langgraph.store.memory import InMemoryStorein_memory_store = InMemoryStore()# Namespace for the memory to saveuser_id = "1"namespace_for_memory = (user_id, "memories")# Save a memory to namespace as key and valuekey = str(uuid.uuid4())# The value needs to be a dictionaryvalue = {"food_preference" : "I like pizza"}# Save the memoryin_memory_store.put(namespace_for_memory, key, value)Copied
O objeto in_memory_store que criamos tem vários métodos e um deles é search, que nos permite pesquisar por namespace
InputPython# Searchmemories = in_memory_store.search(namespace_for_memory)type(memories), len(memories)Copied
(list, 1)
É uma lista de um único valor, o que é lógico, porque só armazenamos um valor, então vamos vê-lo
InputPythonvalue = memories[0]value.dict()Copied
{'namespace': ['1', 'memories'],'key': '70006131-948a-4d7a-bdce-78351c44fc4d','value': {'food_preference': 'I like pizza'},'created_at': '2025-05-11T07:24:31.462465+00:00','updated_at': '2025-05-11T07:24:31.462468+00:00','score': None}
Podemos ver sua key e seu value
InputPython# The key, valuememories[0].key, memories[0].valueCopied
('70006131-948a-4d7a-bdce-78351c44fc4d', {'food_preference': 'I like pizza'})
Também podemos usar o método get para obter um objeto da memória a partir do seu namespace e da sua key
InputPython# Get the memory by namespace and keymemory = in_memory_store.get(namespace_for_memory, key)memory.dict()Copied
{'namespace': ['1', 'memories'],'key': '70006131-948a-4d7a-bdce-78351c44fc4d','value': {'food_preference': 'I like pizza'},'created_at': '2025-05-11T07:24:31.462465+00:00','updated_at': '2025-05-11T07:24:31.462468+00:00'}
Assim como para a memória de curto prazo usamos os checkpoints, para a memória de longo prazo vamos usar LangGraph Store
Chatbot com memória de longo prazo
Criamos um chatbot básico, com memória de longo prazo e memória de curto prazo.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFacefrom langchain_core.messages import HumanMessage, AIMessage, SystemMessagefrom langgraph.checkpoint.memory import MemorySaver # Short-term memoryfrom langgraph.store.base import BaseStore # Long-term memoryfrom langchain_core.runnables.config import RunnableConfigfrom langgraph.store.memory import InMemoryStorefrom 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)# Chatbot instructionMODEL_SYSTEM_MESSAGE = """You are a helpful assistant that can answer questions and help with tasks.You have access to a long-term memory that you can use to answer questions and help with tasks.Here is the memory (it may be empty): {memory}"""# Create new memory from the chat history and any existing memoryCREATE_MEMORY_INSTRUCTION = """You are a helpful assistant that gets information from the user to personalize your responses.# INFORMATION FROM THE USER:{memory}# INSTRUCTIONS:1. Carefully review the chat history2. Identify new information from the user, such as:- Personal details (name, location)- Preferences (likes, dislikes)- Interests and hobbies- Past experiences- Goals or future plans3. Combine any new information with the existing memory4. Format the memory as a clear, bulleted list5. If new information conflicts with existing memory, keep the most recent versionRemember: Only include factual information directly stated by the user. Do not make assumptions or inferences.Based on the chat history below, please update the user information:"""# Nodesdef call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Load memory from the store and use it to personalize the chatbot's response."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve memory from the storenamespace = ("memory", user_id)key = "user_memory"existing_memory = store.get(namespace, key)# Extract the actual memory content if it exists and add a prefixif existing_memory:# Value is a dictionary with a memory keyexisting_memory_content = existing_memory.value.get('memory')else:existing_memory_content = "No existing memory found."if isinstance(existing_memory_content, str):print(f" [Call model debug] Existing memory: {existing_memory_content}")else:print(f" [Call model debug] Existing memory: {existing_memory_content.content}")# Format the memory in the system promptsystem_msg = MODEL_SYSTEM_MESSAGE.format(memory=existing_memory_content)# Respond using memory as well as the chat historyresponse = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])return {"messages": response}def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Reflect on the chat history and save a memory to the store."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve existing memory from the storenamespace = ("memory", user_id)existing_memory = store.get(namespace, "user_memory")# Extract the memoryif existing_memory:existing_memory_content = existing_memory.value.get('memory')else:existing_memory_content = "No existing memory found."if isinstance(existing_memory_content, str):print(f" [Write memory debug] Existing memory: {existing_memory_content}")else:print(f" [Write memory debug] Existing memory: {existing_memory_content.content}")# Format the memory in the system promptsystem_msg = CREATE_MEMORY_INSTRUCTION.format(memory=existing_memory_content)new_memory = llm.invoke([SystemMessage(content=system_msg)]+state['messages'])if isinstance(new_memory, str):print(f" [Write memory debug] New memory: {new_memory}")else:print(f" [Write memory debug] New memory: {new_memory.content}")# Overwrite the existing memory in the storekey = "user_memory"# Write value as a dictionary with a memory keystore.put(namespace, key, {"memory": new_memory.content})# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("call_model", call_model)graph_builder.add_node("write_memory", write_memory)# Connect nodesgraph_builder.add_edge(START, "call_model")graph_builder.add_edge("call_model", "write_memory")graph_builder.add_edge("write_memory", END)# Store for long-term (across-thread) memorylong_term_memory = InMemoryStore()# Checkpointer for short-term (within-thread) memoryshort_term_memory = MemorySaver()# Compile the graphgraph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Vamos testá-lo
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "1", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="Hi, my name is Maximo")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================Hi, my name is Maximo[Call model debug] Existing memory: No existing memory found.================================== Ai Message ==================================Hello Maximo! It's nice to meet you. How can I assist you today?[Write memory debug] Existing memory: No existing memory found.[Write memory debug] New memory:Here's the updated information I have about you:- Name: Maximo
InputPython# User inputinput_messages = [HumanMessage(content="I like to bike around San Francisco")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================I like to bike around San Francisco[Call model debug] Existing memory:Here's the updated information I have about you:- Name: Maximo================================== Ai Message ==================================That sounds like a great way to explore the city! San Francisco has some fantastic biking routes. Are there any specific areas or routes you enjoy biking the most, or are you looking for some new recommendations?[Write memory debug] Existing memory:Here's the updated information I have about you:- Name: Maximo[Write memory debug] New memory:Here's the updated information about you:- Name: Maximo- Location: San Francisco- Interest: Biking around San Francisco
Se recuperarmos a memória de longo prazo
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memory", user_id)existing_memory = long_term_memory.get(namespace, "user_memory")existing_memory.dict()Copied
{'namespace': ['memory', '1'],'key': 'user_memory','value': {'memory': " Here's the updated information about you: - Name: Maximo - Location: San Francisco - Interest: Biking around San Francisco"},'created_at': '2025-05-11T09:41:26.739207+00:00','updated_at': '2025-05-11T09:41:26.739211+00:00'}
Obtemos seu valor
InputPythonprint(existing_memory.value.get('memory'))Copied
Here's the updated information about you:- Name: Maximo- Location: San Francisco- Interest: Biking around San Francisco
Agora podemos começar um novo fio de conversa, mas com a mesma memória de longo prazo. Veremos que o chatbot lembra as informações do usuário.
InputPython# We supply a user ID for across-thread memory as well as a new thread IDconfig = {"configurable": {"thread_id": "2", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="Hi! Where would you recommend that I go biking?")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================Hi! Where would you recommend that I go biking?[Call model debug] Existing memory:Here's the updated information about you:- Name: Maximo- Location: San Francisco- Interest: Biking around San Francisco================================== Ai Message ==================================Hi there! Given my interest in biking around San Francisco, I'd recommend a few great routes:1. **Golden Gate Park**: This is a fantastic place to bike, with wide paths that are separated from vehicle traffic. You can start at the eastern end near Stow Lake and bike all the way to the western end at Ocean Beach. There are plenty of scenic spots to stop and enjoy along the way.2. **The Embarcadero**: This route follows the waterfront from Fisherman’s Wharf to the Bay Bridge. It’s relatively flat and offers beautiful views of the San Francisco Bay and the city skyline. You can also stop by the Ferry Building for some delicious food and drinks.3. **Presidio**: The Presidio is a large park with numerous trails that offer diverse landscapes, from forests to coastal bluffs. The Crissy Field area is especially popular for its views of the Golden Gate Bridge.4. **Golden Gate Bridge**: Riding across the Golden Gate Bridge is a must-do experience. You can start from the San Francisco side, bike across the bridge, and then continue into Marin County for a longer ride with stunning views.5. **Lombard Street**: While not a long ride, biking down the famous crooked section of Lombard Street can be a fun and memorable experience. Just be prepared for the steep hill on the way back up!Each of these routes offers a unique experience, so you can choose based on your interests and the type of scenery you enjoy. Happy biking![Write memory debug] Existing memory:Here's the updated information about you:- Name: Maximo- Location: San Francisco- Interest: Biking around San Francisco[Write memory debug] New memory: 😊Let me know if you have any other questions or if you need more recommendations!
Abri um novo tópico de conversa, perguntei-lhe onde poderia ir andar de bicicleta, ele lembrou-se que eu lhe tinha dito que gosto de andar de bicicleta por São Francisco e respondeu-me com lugares de São Francisco a que eu poderia ir
Chatbot com perfil de usuário
Nota: Esta seção será feita usando Sonnet 3.7, já que a integração do HuggingFace não tem a funcionalidade de
with_structured_output, que fornece uma saída estruturada com uma estrutura definida.
Podemos criar tipos para que o LLM gere uma saída com uma estrutura definida por nós.
Vamos criar uma tipagem para o perfil de usuário.
InputPythonfrom typing import TypedDict, Listclass UserProfile(TypedDict):"""User profile schema with typed fields"""user_name: str # The user's preferred nameinterests: List[str] # A list of the user's interestsCopied
Agora voltamos a criar o grafo, mas agora com a tipagem UserProfile
Vamos a usar with_structured_output para que o LLM gere uma saída com uma estrutura definida por nós; essa estrutura vamos definir com a classe Subjects, que é uma classe do tipo BaseModel do Pydantic.
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_anthropic import ChatAnthropicfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessagefrom langgraph.checkpoint.memory import MemorySaver # Short-term memoryfrom langgraph.store.base import BaseStore # Long-term memoryfrom langchain_core.runnables.config import RunnableConfigfrom langgraph.store.memory import InMemoryStorefrom IPython.display import Image, displayfrom pydantic import BaseModel, Fieldimport osimport dotenvdotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")class State(TypedDict):messages: Annotated[list, add_messages]os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)llm_with_structured_output = llm.with_structured_output(UserProfile)# Chatbot instructionMODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.If you have memory for this user, use it to personalize your responses.Here is the memory (it may be empty): {memory}"""# Create new memory from the chat history and any existing memoryCREATE_MEMORY_INSTRUCTION = """Create or update a user profile memory based on the user's chat history.This will be saved for long-term memory. If there is an existing memory, simply update it.Here is the existing memory (it may be empty): {memory}"""# Nodesdef call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Load memory from the store and use it to personalize the chatbot's response."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve memory from the storenamespace = ("memory", user_id)existing_memory = store.get(namespace, "user_memory")# Format the memories for the system promptif existing_memory and existing_memory.value:memory_dict = existing_memory.valueformatted_memory = (f"Name: {memory_dict.get('user_name', 'Unknown')} "f"Interests: {', '.join(memory_dict.get('interests', []))}")else:formatted_memory = None# if isinstance(existing_memory_content, str):print(f" [Call model debug] Existing memory: {formatted_memory}")# else:# print(f" [Call model debug] Existing memory: {existing_memory_content.content}")# Format the memory in the system promptsystem_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)# Respond using memory as well as the chat historyresponse = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])return {"messages": response}def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Reflect on the chat history and save a memory to the store."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve existing memory from the storenamespace = ("memory", user_id)existing_memory = store.get(namespace, "user_memory")# Format the memories for the system promptif existing_memory and existing_memory.value:memory_dict = existing_memory.valueformatted_memory = (f"Name: {memory_dict.get('user_name', 'Unknown')} "f"Interests: {', '.join(memory_dict.get('interests', []))}")else:formatted_memory = Noneprint(f" [Write memory debug] Existing memory: {formatted_memory}")# Format the existing memory in the instructionsystem_msg = CREATE_MEMORY_INSTRUCTION.format(memory=formatted_memory)# Invoke the model to produce structured output that matches the schemanew_memory = llm_with_structured_output.invoke([SystemMessage(content=system_msg)]+state['messages'])print(f" [Write memory debug] New memory: {new_memory}")# Overwrite the existing use profile memorykey = "user_memory"store.put(namespace, key, new_memory)# Create graph buildergraph_builder = StateGraph(MessagesState)# Add nodesgraph_builder.add_node("call_model", call_model)graph_builder.add_node("write_memory", write_memory)# Connect nodesgraph_builder.add_edge(START, "call_model")graph_builder.add_edge("call_model", "write_memory")graph_builder.add_edge("write_memory", END)# Store for long-term (across-thread) memorylong_term_memory = InMemoryStore()# Checkpointer for short-term (within-thread) memoryshort_term_memory = MemorySaver()# Compile the graphgraph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Executamos o grafo
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "1", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="Hi, my name is Maximo and I like to bike around Madrid and eat salads.")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================Hi, my name is Maximo and I like to bike around Madrid and eat salads.[Call model debug] Existing memory: None================================== Ai Message ==================================Hello Maximo! It's nice to meet you. I see you enjoy biking around Madrid and eating salads - those are great healthy habits! Madrid has some beautiful areas to explore by bike, and the city has been improving its cycling infrastructure in recent years.Is there anything specific about Madrid's cycling routes or perhaps some good places to find delicious salads in the city that you'd like to know more about? I'd be happy to help with any questions you might have.[Write memory debug] Existing memory: None[Write memory debug] New memory: {'user_name': 'Maximo', 'interests': ['biking', 'Madrid', 'salads']}
Como vemos, o LLM gerou uma saída com a estrutura definida por nós.
Vamos ver como a memória de longo prazo foi armazenada.
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memory", user_id)existing_memory = long_term_memory.get(namespace, "user_memory")existing_memory.valueCopied
{'user_name': 'Maximo', 'interests': ['biking', 'Madrid', 'salads']}
Mais
Atualizar esquemas estruturados com Trustcall
No exemplo anterior, criamos perfis de usuário com dados estruturados
Na realidade, o que se faz por baixo é regenerar o perfil de usuário em cada interação. Isso gera um gasto desnecessário de tokens e pode fazer com que se perca informação importante do perfil do usuário.
Então, para resolvê-lo, vamos usar a biblioteca TrustCall, que é uma biblioteca open source para atualizar esquemas JSON. Quando precisa atualizar um esquema JSON, ela o faz de maneira incremental, ou seja, não apaga o esquema anterior, mas vai adicionando os novos campos.
Vamos criar um exemplo de conversa para ver como funciona.
InputPythonfrom langchain_core.messages import HumanMessage, AIMessage# Conversationconversation = [HumanMessage(content="Hi, I'm Maximo."),AIMessage(content="Nice to meet you, Maximo."),HumanMessage(content="I really like playing soccer.")]Copied
Criamos um esquema estruturado e um modelo de LLM
InputPythonfrom pydantic import BaseModel, Fieldfrom typing import List# Schemaclass UserProfile(BaseModel):"""User profile schema with typed fields"""user_name: str = Field(description="The user's preferred name")interests: List[str] = Field(description="A list of the user's interests")from langchain_anthropic import ChatAnthropicimport osimport dotenvdotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)Copied
Utilizamos a função create_extractor de trustcall para criar um extrator de dados estruturados
InputPythonfrom trustcall import create_extractor# Create the extractortrustcall_extractor = create_extractor(llm,tools=[UserProfile],tool_choice="UserProfile")Copied
Como se pode ver, ao método trustcall_extractor é dado um llm, que será usado como motor de busca
Extraímos os dados estruturados
InputPythonfrom langchain_core.messages import SystemMessage# Instructionsystem_msg = "Extract the user profile from the following conversation"# Invoke the extractorresult = trustcall_extractor.invoke({"messages": [SystemMessage(content=system_msg)]+conversation})resultCopied
{'messages': [AIMessage(content=[{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'input': {'user_name': 'Maximo', 'interests': ['soccer']}, 'name': 'UserProfile', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01TEB3FeDKLAeHJtbKo5noyW', 'model': 'claude-3-7-sonnet-20250219', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 497, 'output_tokens': 56}, 'model_name': 'claude-3-7-sonnet-20250219'}, id='run-8a15289b-fd39-4a2d-878a-fa6feaa805c5-0', tool_calls=[{'name': 'UserProfile', 'args': {'user_name': 'Maximo', 'interests': ['soccer']}, 'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'type': 'tool_call'}], usage_metadata={'input_tokens': 497, 'output_tokens': 56, 'total_tokens': 553, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}})],'responses': [UserProfile(user_name='Maximo', interests=['soccer'])],'response_metadata': [{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY'}],'attempts': 1}
Vamos ver as mensagens que foram geradas para extrair os dados estruturados
InputPythonfor m in result["messages"]:m.pretty_print()Copied
================================== Ai Message ==================================[{'id': 'toolu_01WfgbD1fG3rJYAXGrjqjfVY', 'input': {'user_name': 'Maximo', 'interests': ['soccer']}, 'name': 'UserProfile', 'type': 'tool_use'}]Tool Calls:UserProfile (toolu_01WfgbD1fG3rJYAXGrjqjfVY)Call ID: toolu_01WfgbD1fG3rJYAXGrjqjfVYArgs:user_name: Maximointerests: ['soccer']
O esquema de UserProfile foi atualizado com o novo dado.
InputPythonschema = result["responses"]schemaCopied
[UserProfile(user_name='Maximo', interests=['soccer'])]
Como vemos, o esquema é uma lista, vamos ver o tipo de dado do seu único elemento
InputPythontype(schema[0])Copied
__main__.UserProfile
Podemos convertê-lo em um dicionário com model_dump
InputPythonschema[0].model_dump()Copied
{'user_name': 'Maximo', 'interests': ['soccer']}
Graças a ter dado um LLM ao trustcall_extractor, podemos pedir-lhe o que queremos que extraia
Vamos simular que a conversa continua para ver como o esquema é atualizado
InputPython# Update the conversationupdated_conversation = [HumanMessage(content="Hi, I'm Maximo."),AIMessage(content="Nice to meet you, Maximo."),HumanMessage(content="I really like playing soccer."),AIMessage(content="It is great to play soccer! Where do you go after playing soccer?"),HumanMessage(content="I really like to go to a bakery after playing soccer."),]Copied
Pedimos ao modelo que atualize o esquema (um JSON) por meio da biblioteca trustcall
InputPython# Update the instructionsystem_msg = f"""Update the memory (JSON doc) to incorporate new information from the following conversation"""# Invoke the extractor with the updated instruction and existing profile with the corresponding tool name (UserProfile)result = trustcall_extractor.invoke({"messages": [SystemMessage(content=system_msg)]+updated_conversation},{"existing": {"UserProfile": schema[0].model_dump()}})resultCopied
{'messages': [AIMessage(content=[{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'input': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'name': 'UserProfile', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_01RYUJvCdzL4b8kBYKo4BtQf', 'model': 'claude-3-7-sonnet-20250219', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 538, 'output_tokens': 60}, 'model_name': 'claude-3-7-sonnet-20250219'}, id='run-06994472-5ba0-46cc-a512-5fcacce283fc-0', tool_calls=[{'name': 'UserProfile', 'args': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'type': 'tool_call'}], usage_metadata={'input_tokens': 538, 'output_tokens': 60, 'total_tokens': 598, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}})],'responses': [UserProfile(user_name='Maximo', interests=['soccer', 'bakeries'])],'response_metadata': [{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb'}],'attempts': 1}
Vamos ver as mensagens que foram geradas para atualizar o esquema
InputPythonfor m in result["messages"]:m.pretty_print()Copied
================================== Ai Message ==================================[{'id': 'toolu_01K1zTh33kXDAw1h18Yh2HBb', 'input': {'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}, 'name': 'UserProfile', 'type': 'tool_use'}]Tool Calls:UserProfile (toolu_01K1zTh33kXDAw1h18Yh2HBb)Call ID: toolu_01K1zTh33kXDAw1h18Yh2HBbArgs:user_name: Maximointerests: ['soccer', 'bakeries']
Vemos o esquema atualizado
InputPythonupdated_schema = result["responses"][0]updated_schema.model_dump()Copied
{'user_name': 'Maximo', 'interests': ['soccer', 'bakeries']}
Chatbot com perfil de usuário atualizado com Trustcall
Vamos recriar o grafo que atualiza o perfil do usuário, mas agora com a biblioteca trustcall
InputPythonfrom pydantic import BaseModel, Fieldfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_anthropic import ChatAnthropicfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessagefrom langgraph.checkpoint.memory import MemorySaver # Short-term memoryfrom langgraph.store.base import BaseStore # Long-term memoryfrom langchain_core.runnables.config import RunnableConfigfrom langgraph.store.memory import InMemoryStorefrom IPython.display import Image, displayfrom pydantic import BaseModel, Fieldimport osimport dotenvfrom trustcall import create_extractordotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Schemaclass UserProfile(BaseModel):""" Profile of a user """user_name: str = Field(description="The user's preferred name")user_location: str = Field(description="The user's location")interests: list = Field(description="A list of the user's interests")# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)# Create the extractortrustcall_extractor = create_extractor(llm,tools=[UserProfile],tool_choice="UserProfile", # Enforces use of the UserProfile tool)# Chatbot instructionMODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.If you have memory for this user, use it to personalize your responses.Here is the memory (it may be empty): {memory}"""# Create new memory from the chat history and any existing memoryTRUSTCALL_INSTRUCTION = """Create or update the memory (JSON doc) to incorporate information from the following conversation:"""# Nodesdef call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Load memory from the store and use it to personalize the chatbot's response.""""""Load memory from the store and use it to personalize the chatbot's response."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve memory from the storenamespace = ("memory", user_id)existing_memory = store.get(namespace, "user_memory")# Format the memories for the system promptif existing_memory and existing_memory.value:memory_dict = existing_memory.valueformatted_memory = (f"Name: {memory_dict.get('user_name', 'Unknown')} "f"Location: {memory_dict.get('user_location', 'Unknown')} "f"Interests: {', '.join(memory_dict.get('interests', []))}")else:formatted_memory = Noneprint(f" [Call model debug] Existing memory: {formatted_memory}")# Format the memory in the system promptsystem_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)# Respond using memory as well as the chat historyresponse = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])return {"messages": response}def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Reflect on the chat history and save a memory to the store."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve existing memory from the storenamespace = ("memory", user_id)existing_memory = store.get(namespace, "user_memory")# Get the profile as the value from the list, and convert it to a JSON docexisting_profile = {"UserProfile": existing_memory.value} if existing_memory else Noneprint(f" [Write memory debug] Existing profile: {existing_profile}")# Invoke the extractorresult = trustcall_extractor.invoke({"messages": [SystemMessage(content=TRUSTCALL_INSTRUCTION)]+state["messages"], "existing": existing_profile})# Get the updated profile as a JSON objectupdated_profile = result["responses"][0].model_dump()print(f" [Write memory debug] Updated profile: {updated_profile}")# Save the updated profilekey = "user_memory"store.put(namespace, key, updated_profile)# Create graph buildergraph_builder = StateGraph(MessagesState)# Add nodesgraph_builder.add_node("call_model", call_model)graph_builder.add_node("write_memory", write_memory)# Connect nodesgraph_builder.add_edge(START, "call_model")graph_builder.add_edge("call_model", "write_memory")graph_builder.add_edge("write_memory", END)# Store for long-term (across-thread) memorylong_term_memory = InMemoryStore()# Checkpointer for short-term (within-thread) memoryshort_term_memory = MemorySaver()# Compile the graphgraph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Iniciamos a conversa
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "1", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="Hi, my name is Maximo")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================Hi, my name is Maximo[Call model debug] Existing memory: None================================== Ai Message ==================================Hello Maximo! It's nice to meet you. How can I help you today? Whether you have questions, need information, or just want to chat, I'm here to assist you. Is there something specific you'd like to talk about?[Write memory debug] Existing profile: None[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': '<UNKNOWN>', 'interests': []}
Como vemos, não sabe nem a localização nem os interesses do usuário. Vamos atualizar o perfil do usuário.
InputPython# User inputinput_messages = [HumanMessage(content="I like to play soccer and I live in Madrid")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================I like to play soccer and I live in Madrid[Call model debug] Existing memory: Name: MaximoLocation: <UNKNOWN>Interests:================================== Ai Message ==================================Hello Maximo! It's great to learn that you live in Madrid and enjoy playing soccer. Madrid is a fantastic city with a rich soccer culture, being home to world-famous clubs like Real Madrid and Atlético Madrid.Soccer is truly a way of life in Spain, so you're in a perfect location for your interest. Do you support any particular team in Madrid? Or perhaps you enjoy playing soccer recreationally in the city's parks and facilities?Is there anything specific about Madrid or soccer you'd like to discuss further?[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': '<UNKNOWN>', 'interests': []}}[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}
Atualizou o perfil com a localização e os interesses do usuário
Vamos ver a memória atualizada
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memory", user_id)existing_memory = long_term_memory.get(namespace, "user_memory")existing_memory.dict()Copied
{'namespace': ['memory', '1'],'key': 'user_memory','value': {'user_name': 'Maximo','user_location': 'Madrid','interests': ['soccer']},'created_at': '2025-05-12T17:35:03.583258+00:00','updated_at': '2025-05-12T17:35:03.583259+00:00'}
Vemos o esquema com o perfil do usuário atualizado
InputPython# The user profile saved as a JSON objectexisting_memory.valueCopied
{'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}
Vamos adicionar um novo interesse do usuário
InputPython# User inputinput_messages = [HumanMessage(content="I also like to play basketball")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================I also like to play basketball[Call model debug] Existing memory: Name: MaximoLocation: MadridInterests: soccer================================== Ai Message ==================================That's great to know, Maximo! It's nice that you enjoy both soccer and basketball. Basketball is also quite popular in Spain, with Liga ACB being one of the strongest basketball leagues in Europe.In Madrid, you have the opportunity to follow Real Madrid's basketball section, which is one of the most successful basketball teams in Europe. The city offers plenty of courts and facilities where you can play basketball too.Do you play basketball casually with friends, or are you part of any local leagues in Madrid? And how do you balance your time between soccer and basketball?[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer']}}[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}
Voltamos a ver a memória atualizada
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memory", user_id)existing_memory = long_term_memory.get(namespace, "user_memory")existing_memory.valueCopied
{'user_name': 'Maximo','user_location': 'Madrid','interests': ['soccer', 'basketball']}
Ele adicionou corretamente o novo interesse do usuário.
Com esta memória de longo prazo guardada, podemos iniciar um novo tópico e o chatbot terá acesso ao nosso perfil atualizado.
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "2", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="What soccer players do you recommend for me?")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================What soccer players do you recommend for me?[Call model debug] Existing memory: Name: MaximoLocation: MadridInterests: soccer, basketball================================== Ai Message ==================================Based on your interest in soccer, I can recommend some players who might appeal to you. Since you're from Madrid, you might already follow Real Madrid or Atlético Madrid players, but here are some recommendations:From La Liga:- Vinícius Júnior and Jude Bellingham (Real Madrid)- Antoine Griezmann (Atlético Madrid)- Robert Lewandowski (Barcelona)- Lamine Yamal (Barcelona's young talent)International stars:- Kylian Mbappé- Erling Haaland- Mohamed Salah- Kevin De BruyneYou might also enjoy watching players with creative playing styles since you're interested in basketball as well, which is a sport that values creativity and flair - players like Rodrigo De Paul or João Félix.Is there a particular league or playing style you prefer in soccer?[Write memory debug] Existing profile: {'UserProfile': {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}}[Write memory debug] Updated profile: {'user_name': 'Maximo', 'user_location': 'Madrid', 'interests': ['soccer', 'basketball']}
Como ele sabe que vivo em Madrid, primeiro me sugeriu jogadores de futebol da Liga espanhola. E depois me sugeriu jogadores de outras ligas
Chatbot com coleções de documentos de usuário atualizadas com Trustcall
Outro enfoque é, em vez de guardar em um único documento o perfil do usuário, guardar uma coleção de documentos; dessa forma, não ficamos presos a um único esquema fechado
Vamos ver como fazer isso
InputPythonfrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langchain_anthropic import ChatAnthropicfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessagefrom langchain_core.messages import merge_message_runsfrom langgraph.checkpoint.memory import MemorySaver # Short-term memoryfrom langgraph.store.base import BaseStore # Long-term memoryfrom langchain_core.runnables.config import RunnableConfigfrom langgraph.store.memory import InMemoryStorefrom IPython.display import Image, displayfrom trustcall import create_extractorfrom pydantic import BaseModel, Fieldimport uuidimport osimport dotenvdotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Memory schemaclass Memory(BaseModel):"""A memory item representing a piece of information learned about the user."""content: str = Field(description="The main content of the memory. For example: User expressed interest in learning about French.")# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)# Create the extractortrustcall_extractor = create_extractor(llm,tools=[Memory],tool_choice="Memory",# This allows the extractor to insert new memoriesenable_inserts=True,)# Chatbot instructionMODEL_SYSTEM_MESSAGE = """You are a helpful chatbot. You are designed to be a companion to a user.You have a long term memory which keeps track of information you learn about the user over time.Current Memory (may include updated memories from this conversation):{memory}"""# Create new memory from the chat history and any existing memoryTRUSTCALL_INSTRUCTION = """Reflect on following interaction.Use the provided tools to retain any necessary memories about the user.Use parallel tool calling to handle updates and insertions simultaneously:"""# Nodesdef call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Load memory from the store and use it to personalize the chatbot's response."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Retrieve memory from the storenamespace = ("memories", user_id)memories = store.search(namespace)print(f" [Call model debug] Memories: {memories}")# Format the memories for the system promptinfo = " ".join(f"- {mem.value['content']}" for mem in memories)system_msg = MODEL_SYSTEM_MESSAGE.format(memory=info)# Respond using memory as well as the chat historyresponse = llm.invoke([SystemMessage(content=system_msg)]+state["messages"])return {"messages": response}def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):"""Reflect on the chat history and save a memory to the store."""# Get the user ID from the configuser_id = config["configurable"]["user_id"]# Define the namespace for the memoriesnamespace = ("memories", user_id)# Retrieve the most recent memories for contextexisting_items = store.search(namespace)# Format the existing memories for the Trustcall extractortool_name = "Memory"existing_memories = ([(existing_item.key, tool_name, existing_item.value)for existing_item in existing_items]if existing_itemselse None)print(f" [Write memory debug] Existing memories: {existing_memories}")# Merge the chat history and the instructionupdated_messages=list(merge_message_runs(messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION)] + state["messages"]))# Invoke the extractorresult = trustcall_extractor.invoke({"messages": updated_messages,"existing": existing_memories})# Save the memories from Trustcall to the storefor r, rmeta in zip(result["responses"], result["response_metadata"]):store.put(namespace,rmeta.get("json_doc_id", str(uuid.uuid4())),r.model_dump(mode="json"),)print(f" [Write memory debug] Saved memories: {result['responses']}")# Create graph buildergraph_builder = StateGraph(MessagesState)# Add nodesgraph_builder.add_node("call_model", call_model)graph_builder.add_node("write_memory", write_memory)# Connect nodesgraph_builder.add_edge(START, "call_model")graph_builder.add_edge("call_model", "write_memory")graph_builder.add_edge("write_memory", END)# Store for long-term (across-thread) memorylong_term_memory = InMemoryStore()# Checkpointer for short-term (within-thread) memoryshort_term_memory = MemorySaver()# Compile the graphgraph = graph_builder.compile(checkpointer=short_term_memory, store=long_term_memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Começamos uma nova conversa
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "1", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="Hi, my name is Maximo")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================Hi, my name is Maximo[Call model debug] Memories: []================================== Ai Message ==================================Hello Maximo! It's nice to meet you. I'm your companion chatbot, here to chat, help answer questions, or just be someone to talk to.I'll remember your name is Maximo for our future conversations. What would you like to talk about today? How are you doing?[Write memory debug] Existing memories: None[Write memory debug] Saved memories: [Memory(content="User's name is Maximo.")]
Adicionamos um novo interesse do usuário
InputPython# User inputinput_messages = [HumanMessage(content="I like to play soccer")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================I like to play soccer[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None)]================================== Ai Message ==================================That's great to know, Maximo! Soccer is such a wonderful sport. Do you play on a team, or more casually with friends? I'd also be curious to know what position you typically play, or if you have a favorite professional team you follow. I'll remember that you enjoy soccer for our future conversations.[Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."})][Write memory debug] Saved memories: [Memory(content='User enjoys playing soccer.')]
Como vemos, o novo interesse do usuário foi adicionado à memória.
Vamos ver a memória atualizada
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memories", user_id)memories = long_term_memory.search(namespace)for m in memories:print(m.dict())Copied
{'namespace': ['memories', '1'], 'key': '6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'value': {'content': "User's name is Maximo."}, 'created_at': '2025-05-12T18:32:38.070902+00:00', 'updated_at': '2025-05-12T18:32:38.070903+00:00', 'score': None}{'namespace': ['memories', '1'], 'key': '25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'value': {'content': 'User enjoys playing soccer.'}, 'created_at': '2025-05-12T18:32:42.558787+00:00', 'updated_at': '2025-05-12T18:32:42.558789+00:00', 'score': None}
InputPythonfor m in memories:print(m.value)Copied
{'content': "User's name is Maximo."}{'content': 'User enjoys playing soccer.'}
Vemos que são armazenados documentos de memória, não um perfil do usuário.
Vamos adicionar um novo interesse do usuário
InputPython# User inputinput_messages = [HumanMessage(content="I also like to play basketball")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================I also like to play basketball[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None), Item(namespace=['memories', '1'], key='25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', value={'content': 'User enjoys playing soccer.'}, created_at='2025-05-12T18:32:42.558787+00:00', updated_at='2025-05-12T18:32:42.558789+00:00', score=None)]================================== Ai Message ==================================That's awesome, Maximo! Both soccer and basketball are fantastic sports. I'll remember that you enjoy basketball as well. Do you find yourself playing one more than the other? And similar to soccer, do you play basketball with a team or more casually? Many people enjoy the different skills and dynamics each sport offers - soccer with its continuous flow and footwork, and basketball with its fast pace and shooting precision. Any favorite basketball teams you follow?[Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."}), ('25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'Memory', {'content': 'User enjoys playing soccer.'})][Write memory debug] Saved memories: [Memory(content='User enjoys playing basketball.')]
Voltamos a ver a memória atualizada
InputPython# Namespace for the memory to saveuser_id = "1"namespace = ("memories", user_id)memories = long_term_memory.search(namespace)for m in memories:print(m.value)Copied
{'content': "User's name is Maximo."}{'content': 'User enjoys playing soccer.'}{'content': 'User enjoys playing basketball.'}
Iniciamos uma nova conversa com um novo fio
InputPython# We supply a thread ID for short-term (within-thread) memory# We supply a user ID for long-term (across-thread) memoryconfig = {"configurable": {"thread_id": "2", "user_id": "1"}}# User inputinput_messages = [HumanMessage(content="What soccer players do you recommend for me?")]# Run the graphfor chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):chunk["messages"][-1].pretty_print()Copied
================================ Human Message =================================What soccer players do you recommend for me?[Call model debug] Memories: [Item(namespace=['memories', '1'], key='6d06c4f5-3a74-46b2-92b4-1e29ba128c90', value={'content': "User's name is Maximo."}, created_at='2025-05-12T18:32:38.070902+00:00', updated_at='2025-05-12T18:32:38.070903+00:00', score=None), Item(namespace=['memories', '1'], key='25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', value={'content': 'User enjoys playing soccer.'}, created_at='2025-05-12T18:32:42.558787+00:00', updated_at='2025-05-12T18:32:42.558789+00:00', score=None), Item(namespace=['memories', '1'], key='965f2e52-bea0-44d4-8534-4fce2bbc1c4b', value={'content': 'User enjoys playing basketball.'}, created_at='2025-05-12T18:33:38.613626+00:00', updated_at='2025-05-12T18:33:38.613629+00:00', score=None)]================================== Ai Message ==================================Hi Maximo! Since you enjoy soccer, I'd be happy to recommend some players you might find interesting to follow or learn from.Based on your interests in both soccer and basketball, I might suggest players who are known for their athleticism and skill:1. Lionel Messi - Widely considered one of the greatest players of all time2. Cristiano Ronaldo - Known for incredible athleticism and dedication3. Kylian Mbappé - Young talent with amazing speed and technical ability4. Kevin De Bruyne - Master of passing and vision5. Erling Haaland - Goal-scoring phenomenonIs there a particular position or playing style you're most interested in? That would help me refine my recommendations further. I could also suggest players from specific leagues or teams if you have preferences![Write memory debug] Existing memories: [('6d06c4f5-3a74-46b2-92b4-1e29ba128c90', 'Memory', {'content': "User's name is Maximo."}), ('25d2ee8c-5890-415b-85e0-d9fb0ea4cd43', 'Memory', {'content': 'User enjoys playing soccer.'}), ('965f2e52-bea0-44d4-8534-4fce2bbc1c4b', 'Memory', {'content': 'User enjoys playing basketball.'})][Write memory debug] Saved memories: [Memory(content='User asked for soccer player recommendations, suggesting an active interest in following professional soccer beyond just playing it.')]
Vemos que ele se lembrava de que gostávamos de futebol e basquetebol.
Humano no loop
Embora um agente possa realizar tarefas, para certas tarefas, é necessário que haja supervisão humana. A isso se chama human in the loop. Então, vamos ver como isso pode ser feito com LangGraph.
A camada de persistência do LangGraph oferece suporte a fluxos de trabalho com humanos no loop, permitindo que a execução seja pausada e retomada com base no feedback dos usuários. A interface principal dessa funcionalidade é a função interrupt. Chamar interrupt dentro de um nó interromperá a execução. A execução pode ser retomada, junto com a nova contribuição humana, passada em uma primitiva Command. interrupt é semelhante ao comando input() do Python, mas com algumas considerações extras.
Vamos adicionar ao chatbot que tem memória de curto prazo e acesso a tools, mas faremos uma mudança, que é adicionar uma ferramenta simples human_assistance. Esta ferramenta utiliza interrupt para receber informações de um humano.
Primeiro carregamos os valores das API KEYs
InputPythonimport osimport dotenvdotenv.load_dotenv()HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_LANGGRAPH")TAVILY_API_KEY = os.getenv("TAVILY_LANGGRAPH_API_KEY")Copied
Criamos o 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 a tool de busca
InputPythonfrom langchain_community.utilities.tavily_search import TavilySearchAPIWrapperfrom langchain_community.tools.tavily_search import TavilySearchResultswrapper = TavilySearchAPIWrapper(tavily_api_key=TAVILY_API_KEY)search_tool = TavilySearchResults(api_wrapper=wrapper, max_results=2)Copied
Agora criamos a tool de ajuda humana
InputPythonfrom langgraph.types import Command, interruptfrom langchain_core.tools import tool@tooldef human_assistance(query: str) -> str:"""Request assistance from a human expert. Use this tool ONLY ONCE per conversation.After receiving the expert's response, you should provide an elaborated response to the user based on the information receivedbased on the information received, without calling this tool again.Args:query: The query to ask the human expert.Returns:The response from the human expert."""human_response = interrupt({"query": query})return human_response["data"]Copied
LangGraph obtém informações das ferramentas por meio da documentação da ferramenta, ou seja, o docstring da função. Portanto, é muito importante gerar um bom docstring para a ferramenta.
Criamos uma lista de tools
InputPythontools_list = [search_tool, human_assistance]Copied
Em seguida, o LLM com as bind_tools e o adicionamos ao 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)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 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):message = llm_with_tools.invoke(state["messages"])assert len(message.tool_calls) <= 1return {"messages": [message]}# Add the chatbot nodegraph_builder.add_node("chatbot_node", chatbot_function)Copied
<langgraph.graph.state.StateGraph at 0x10764b380>
Se você notar, mudamos a forma de definir a função chatbot_function, já que agora ela precisa lidar com a interrupção.
Adicionamos o tool_node ao grafo
InputPythonfrom langgraph.prebuilt import ToolNode, tools_conditiontool_node = ToolNode(tools=tools_list)graph_builder.add_node("tools", tool_node)graph_builder.add_conditional_edges("chatbot_node", tools_condition)graph_builder.add_edge("tools", "chatbot_node")Copied
<langgraph.graph.state.StateGraph at 0x10764b380>
Adicionamos o nó START ao grafo
InputPythongraph_builder.add_edge(START, "chatbot_node")Copied
<langgraph.graph.state.StateGraph at 0x10764b380>
Criamos um checkpointer MemorySaver.
InputPythonfrom langgraph.checkpoint.memory import MemorySavermemory = MemorySaver()Copied
Compilamos o grafo com o checkpointer
InputPythongraph = graph_builder.compile(checkpointer=memory)Copied
Representamos graficamente
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>
Agora solicitemos ao chatbot com uma pergunta que envolverá a nova ferramenta human_assistance:
InputPythonuser_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"config = {"configurable": {"thread_id": "1"}}events = graph.stream({"messages": [{"role": "user", "content": user_input}]},config,stream_mode="values",)for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================ Human Message =================================I need some expert guidance for building an AI agent. Could you request assistance for me?================================== Ai Message ==================================Tool Calls:human_assistance (0)Call ID: 0Args:query: I need some expert guidance for building an AI agent. Could you provide me with some advice?
Como se pode ver, o chatbot gerou uma chamada à ferramenta de assistência humana.
Chamadas de ferramentas:
assistência humana (0)
ID da chamada: 0
Args:consulta: Preciso de orientação especializada para construir um agente de IA. Poderia fornecer conselhos sobre considerações principais, melhores práticas e possíveis armadilhas a evitar?Mas depois a execução foi interrompida. Vamos ver o estado do grafo
InputPythonsnapshot = graph.get_state(config)snapshot.nextCopied
('tools',)
Vemos que se deteve no nó de tools. Analisamos como foi definida a ferramenta human_assistance.
from langgraph.types import Command, interrupt
from langchain_core.tools import tool
@toolpython
def human_assistance(query: str) -> str:
"""
Solicite assistência a um especialista humano. Use esta ferramenta APENAS UMA VEZ por conversa.
Após receber a resposta do especialista, você deve fornecer uma resposta elaborada ao usuário com base nas informações recebidas
com base nas informações recebidas, sem chamar esta ferramenta novamente.
Args:
consulta: A consulta para perguntar ao especialista humano.
Retornos:
A resposta do especialista humano.
"""
human_response = interrupt({"query": query})
return human_response["data"]Chamando a ferramenta interrupt irá parar a execução, semelhante à função input() do Python.
O progresso se mantém em função de nossa escolha de checkpointer. Ou seja, a escolha de onde o estado do grafo é salvo. Então, se estivermos persistindo (salvando o estado do grafo) com um banco de dados como SQLite, Postgres, etc., podemos retomar a execução a qualquer momento, desde que o banco de dados esteja ativo.
Aqui estamos persistindo (guardando o estado do grafo) com o ponteiro de verificação na memória RAM, por isso podemos retomar a qualquer momento enquanto nosso kernel do Python estiver em execução. No meu caso, enquanto eu não reiniciar o kernel do meu Jupyter Notebook.
Para retomar a execução, passamos um objeto Command que contém os dados esperados pela ferramenta. O formato desses dados pode ser personalizado de acordo com as nossas necessidades. Aqui, só precisamos de um dicionário com uma chave data
InputPythonhuman_response = ("We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.""It's much more reliable and extensible than simple autonomous agents.")human_command = Command(resume={"data": human_response})events = graph.stream(human_command, config, stream_mode="values")for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================== Ai Message ==================================Tool Calls:human_assistance (0)Call ID: 0Args:query: I need some expert guidance for building an AI agent. Could you provide me with some advice?================================= Tool Message =================================Name: human_assistanceWe, the experts are here to help! We'd recommend you check out LangGraph to build your agent.It's much more reliable and extensible than simple autonomous agents.================================== Ai Message ==================================The experts recommend checking out LangGraph for building your AI agent. It's known for being more reliable and extensible compared to simple autonomous agents.
Como vemos, o chatbot esperou que um humano lhe fornecesse a resposta e, em seguida, gerou uma resposta com base nas informações recebidas. Pedimos ajuda sobre um especialista em como criar agentes, o humano disse que o melhor é usar LangGraph, e o chatbot gerou uma resposta com base nessas informações.
Mas ainda tem a possibilidade de realizar pesquisas na web. Então agora vamos pedir as últimas notícias sobre LangGraph.
InputPythonuser_input = "What's the latest news about LangGraph?"events = graph.stream({"messages": [{"role": "user", "content": user_input}]},config,stream_mode="values",)for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================ Human Message =================================What's the latest news about LangGraph?================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args:query: latest news LangGraph================================= Tool Message =================================Name: tavily_search_results_json[{"title": "LangChain - Changelog", "url": "https://changelog.langchain.com/", "content": "LangGraph `interrupt`: Simplifying human-in-the-loop agents --------------------------------------------------- Our latest feature in LangGraph, interrupt , makes building human-in-the-loop workflows easier. Agents aren’t perfect, so keeping humans “in the loop”... December 16, 2024 [...] LangGraph 🔁 Modify graph state from tools in LangGraph --------------------------------------------- LangGraph's latest update gives you greater control over your agents by enabling tools to directly update the graph state. This is a game-changer for use... December 18, 2024 [...] LangGraph Platform Custom authentication & access control for LangGraph Platform ------------------------------------------------------------- Today, we're thrilled to announce Custom Authentication and Resource-Level Access Control for Python deployments in LangGraph Cloud and self-hosted... December 20, 2024", "score": 0.78650844}, {"title": "LangGraph 0.3 Release: Prebuilt Agents - LangChain Blog", "url": "https://blog.langchain.dev/langgraph-0-3-release-prebuilt-agents/", "content": "LangGraph 0.3 Release: Prebuilt Agents 2 min read Feb 27, 2025 By Nuno Campos and Vadym Barda Over the past year, we’ve invested heavily in making LangGraph the go-to framework for building AI agents. With companies like Replit, Klarna, LinkedIn and Uber choosing to build on top of LangGraph, we have more conviction than ever that we are on the right path. [...] Up to this point, we’ve had one higher level abstraction and it’s lived in the main langgraph package. It was create_react_agent, a wrapper for creating a simple tool calling agent. Today, we are splitting that out of langgraph as part of a 0.3 release, and moving it into langgraph-prebuilt. We are also introducing a new set of prebuilt agents built on top of LangGraph, in both Python and JavaScript. Over the past three weeks, we’ve already released a few of these: [...] Published Time: 2025-02-27T15:09:15.000Z LangGraph 0.3 Release: Prebuilt Agents Skip to content Case Studies In the Loop LangChain Docs Changelog Sign in Subscribe", "score": 0.72348577}]================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args:query: latest news about LangGraph================================= Tool Message =================================Name: tavily_search_results_json[{"title": "LangChain - Changelog", "url": "https://changelog.langchain.com/", "content": "LangGraph 🔁 Modify graph state from tools in LangGraph --------------------------------------------- LangGraph's latest update gives you greater control over your agents by enabling tools to directly update the graph state. This is a game-changer for use... December 18, 2024 [...] LangGraph `interrupt`: Simplifying human-in-the-loop agents --------------------------------------------------- Our latest feature in LangGraph, interrupt , makes building human-in-the-loop workflows easier. Agents aren’t perfect, so keeping humans “in the loop”... December 16, 2024 [...] LangGraph Platform Custom authentication & access control for LangGraph Platform ------------------------------------------------------------- Today, we're thrilled to announce Custom Authentication and Resource-Level Access Control for Python deployments in LangGraph Cloud and self-hosted... December 20, 2024", "score": 0.79732054}, {"title": "LangGraph 0.3 Release: Prebuilt Agents - LangChain Blog", "url": "https://blog.langchain.dev/langgraph-0-3-release-prebuilt-agents/", "content": "LangGraph 0.3 Release: Prebuilt Agents 2 min read Feb 27, 2025 By Nuno Campos and Vadym Barda Over the past year, we’ve invested heavily in making LangGraph the go-to framework for building AI agents. With companies like Replit, Klarna, LinkedIn and Uber choosing to build on top of LangGraph, we have more conviction than ever that we are on the right path. [...] Up to this point, we’ve had one higher level abstraction and it’s lived in the main langgraph package. It was create_react_agent, a wrapper for creating a simple tool calling agent. Today, we are splitting that out of langgraph as part of a 0.3 release, and moving it into langgraph-prebuilt. We are also introducing a new set of prebuilt agents built on top of LangGraph, in both Python and JavaScript. Over the past three weeks, we’ve already released a few of these: [...] Published Time: 2025-02-27T15:09:15.000Z LangGraph 0.3 Release: Prebuilt Agents Skip to content Case Studies In the Loop LangChain Docs Changelog Sign in Subscribe", "score": 0.7552947}]================================== Ai Message ==================================The latest news about LangGraph includes several updates and releases. Firstly, the 'interrupt' feature has been added, which simplifies creating human-in-the-loop workflows, essential for maintaining oversight of AI agents. Secondly, an update allows tools to modify the graph state directly, providing more control over the agents. Lastly, custom authentication and resource-level access control have been implemented for Python deployments in LangGraph Cloud and self-hosted environments. In addition, LangGraph released version 0.3, which introduces prebuilt agents in both Python and JavaScript, aimed at making it even easier to develop AI agents.
Ele pesquisou as últimas notícias sobre LangGraph e gerou uma resposta com base nas informações recebidas.
Vamos escrever tudo junto para que seja mais compreensível
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.types import Command, interruptfrom langchain_core.tools import toolfrom 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_search = TavilySearchResults(api_wrapper=wrapper, max_results=2)@tooldef human_assistance(query: str) -> str:"""Request assistance from a human expert. Use this tool ONLY ONCE per conversation.After receiving the expert's response, you should provide an elaborated response to the user based on the information receivedbased on the information received, without calling this tool again.Args:query: The query to ask the human expert.Returns:The response from the human expert."""human_response = interrupt({"query": query})return human_response["data"]tools_list = [tool_search, human_assistance]# Create the LLM 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):message = llm_with_tools.invoke(state["messages"])assert len(message.tool_calls) <= 1return {"messages": [message]}# 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)`
Voltamos a pedir ajuda ao chatbot para criar agentes. Pedimos que procure ajuda
InputPythonuser_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"config = {"configurable": {"thread_id": "1"}}events = graph.stream({"messages": [{"role": "user", "content": user_input}]},config,stream_mode="values",)for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================ Human Message =================================I need some expert guidance for building an AI agent. Could you request assistance for me?================================== Ai Message ==================================Tool Calls:human_assistance (0)Call ID: 0Args:query: I need expert guidance for building an AI agent.
Vamos ver em que estado ficou o grafo
InputPythonsnapshot = graph.get_state(config)snapshot.nextCopied
('tools',)
Nós damos a assistência que ele está pedindo
InputPythonhuman_response = ("We, the experts are here to help! We'd recommend you check out LangGraph to build your agent.""It's much more reliable and extensible than simple autonomous agents.")human_command = Command(resume={"data": human_response})events = graph.stream(human_command, config, stream_mode="values")for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================== Ai Message ==================================Tool Calls:human_assistance (0)Call ID: 0Args:query: I need expert guidance for building an AI agent.================================= Tool Message =================================Name: human_assistanceWe, the experts are here to help! We'd recommend you check out LangGraph to build your agent.It's much more reliable and extensible than simple autonomous agents.================================== Ai Message ==================================Tool Calls:human_assistance (0)Call ID: 0Args:query: I need some expert guidance for building an AI agent. Could you recommend a platform and any tips for getting started?
E, por último, pedimos que você busque na internet as últimas notícias do LangGraph
InputPythonuser_input = "What's the latest news about LangGraph?"events = graph.stream({"messages": [{"role": "user", "content": user_input}]},config,stream_mode="values",)for event in events:if "messages" in event:event["messages"][-1].pretty_print()Copied
================================ Human Message =================================What's the latest news about LangGraph?================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args:query: latest news about LangGraph================================= Tool Message =================================Name: tavily_search_results_json[{"title": "LangChain Blog", "url": "https://blog.langchain.dev/", "content": "LangSmith Incident on May 1, 2025 Requests to the US LangSmith API from both the web application and SDKs experienced an elevated error rate for 28 minutes on May 1, 2025 Featured How Klarna's AI assistant redefined customer support at scale for 85 million active users Is LangGraph Used In Production? Introducing Interrupt: The AI Agent Conference by LangChain Top 5 LangGraph Agents in Production 2024 [...] See how Harmonic uses LangSmith and LangGraph products to streamline venture investing workflows. Why Definely chose LangGraph for building their multi-agent AI system See how Definely used LangGraph to design a multi-agent system to help lawyers speed up their workflows. Introducing End-to-End OpenTelemetry Support in LangSmith LangSmith now provides end-to-end OpenTelemetry (OTel) support for applications built on LangChain and/or LangGraph.", "score": 0.6811549}, {"title": "LangGraph + UiPath: advancing agentic automation together", "url": "https://www.uipath.com/blog/product-and-updates/langgraph-uipath-advancing-agentic-automation-together", "content": "Raghu Malpani, Chief Technology Officer at UiPath, emphasizes the significance of these announcements for the UiPath developer community: Our collaboration with LangChain on LangSmith and Agent Protocol advances interoperability across agent frameworks. Further, by enabling the deployment of LangGraph agents into UiPath's enterprise-grade infrastructure, we are expanding the capabilities of our platform and opening up more possibilities for our developer community. [...] Today, we’re excited to announce: Native support for LangSmith observability in the UiPath LLM Gateway via OpenTelemetry (OTLP), enabling developers to monitor, debug, and evaluate LLM-powered features in UiPath using LangSmith either in LangChain’s cloud or self-hosted on-premises. This feature is currently in private preview.", "score": 0.6557114}]================================== Ai Message ==================================Tool Calls:tavily_search_results_json (0)Call ID: 0Args:query: latest news about LangGraph================================= Tool Message =================================Name: tavily_search_results_json[{"title": "LangChain Blog", "url": "https://blog.langchain.dev/", "content": "LangSmith Incident on May 1, 2025 Requests to the US LangSmith API from both the web application and SDKs experienced an elevated error rate for 28 minutes on May 1, 2025 Featured How Klarna's AI assistant redefined customer support at scale for 85 million active users Is LangGraph Used In Production? Introducing Interrupt: The AI Agent Conference by LangChain Top 5 LangGraph Agents in Production 2024 [...] See how Harmonic uses LangSmith and LangGraph products to streamline venture investing workflows. Why Definely chose LangGraph for building their multi-agent AI system See how Definely used LangGraph to design a multi-agent system to help lawyers speed up their workflows. Introducing End-to-End OpenTelemetry Support in LangSmith LangSmith now provides end-to-end OpenTelemetry (OTel) support for applications built on LangChain and/or LangGraph.", "score": 0.6811549}, {"title": "LangGraph + UiPath: advancing agentic automation together", "url": "https://www.uipath.com/blog/product-and-updates/langgraph-uipath-advancing-agentic-automation-together", "content": "Raghu Malpani, Chief Technology Officer at UiPath, emphasizes the significance of these announcements for the UiPath developer community: Our collaboration with LangChain on LangSmith and Agent Protocol advances interoperability across agent frameworks. Further, by enabling the deployment of LangGraph agents into UiPath's enterprise-grade infrastructure, we are expanding the capabilities of our platform and opening up more possibilities for our developer community. [...] Today, we’re excited to announce: Native support for LangSmith observability in the UiPath LLM Gateway via OpenTelemetry (OTLP), enabling developers to monitor, debug, and evaluate LLM-powered features in UiPath using LangSmith either in LangChain’s cloud or self-hosted on-premises. This feature is currently in private preview.", "score": 0.6557114}]...================================= Tool Message =================================Name: tavily_search_results_json[{"title": "LangGraph - LangChain", "url": "https://www.langchain.com/langgraph", "content": "“As Ally advances its exploration of Generative AI, our tech labs is excited by LangGraph, the new library from LangChain, which is central to our experiments", "score": 0.98559}, {"title": "Evaluating LangGraph Framework : Series 1 | by Jalaj Agrawal", "url": "https://medium.com/@jalajagr/evaluating-langgraph-as-a-multiagent-framework-a-10-dimensional-framework-series-1-c7203b7f4659", "content": ": LangGraph excels with its intuitive graph-based abstraction that allows new developers to build working multi-agent systems within hours.", "score": 0.98196}]================================== Ai Message ==================================It looks like LangGraph has been generating some significant buzz in the AI community, especially for its capabilities in building multi-agent systems. Here are a few highlights from the latest news:1. **LangGraph in Production**: Companies like Klarna and Definely are already using LangGraph to build and optimize their AI systems. Klarna has leveraged LangGraph to enhance their customer support, and Definely has used it to design a multi-agent system to speed up legal workflows.2. **Integration with UiPath**: LangChain and UiPath have collaborated to advance agentic automation. This partnership includes native support for LangSmith observability in UiPath’s LLM Gateway via OpenTelemetry, which will allow developers to monitor, debug, and evaluate LLM-powered features more effectively.3. **Intuitive Design**: LangGraph is praised for its intuitive graph-based abstraction, which enables developers to build working multi-agent systems quickly, even if they are new to the field.4. **Community and Conferences**: LangChain is also hosting an AI Agent Conference called "Interrupt," which could be a great opportunity to learn more about the latest developments and best practices in building AI agents.If you're considering using LangGraph for your project, these resources and updates might provide valuable insights and support. Would you like more detailed information on any specific aspect of LangGraph?
Mais
Aprovação do uso de ferramentas
Nota: Esta seção vamos fazer usando Sonnet 3.7, já que, no momento da redação do post, é o melhor modelo para uso com agentes, e é o único que entende quando deve chamar as tools e quando não deve para este exemplo
Podemos inserir um human in the loop para aprovar o uso de ferramentas. Vamos criar um chatbot com várias ferramentas para realizar operações matemáticas; para isso, ao construir o grafo indicamos onde queremos inserir o breakpoint (graph_builder.compile(interrupt_before=["tools"], checkpointer=memory))
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_core.messages import SystemMessage, HumanMessage, AIMessagefrom langgraph.prebuilt import ToolNode, tools_conditionfrom langgraph.checkpoint.memory import MemorySaverfrom langchain_core.tools import toolfrom langchain_anthropic import ChatAnthropicfrom IPython.display import Image, displayimport osimport dotenvdotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")memory = MemorySaver()class State(TypedDict):messages: Annotated[list, add_messages]os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Tools@tooldef multiply(a: int, b: int) -> int:"""Multiply a and b.Args:a: first intb: second intReturns:The product of a and b."""return a * b@tooldef add(a: int, b: int) -> int:"""Adds a and b.Args:a: first intb: second intReturns:The sum of a and b."""return a + b@tooldef subtract(a: int, b: int) -> int:"""Subtract b from a.Args:a: first intb: second intReturns:The difference between a and b."""return a - b@tooldef divide(a: int, b: int) -> float:"""Divide a by b.Args:a: first intb: second intReturns:The quotient of a and b."""return a / btools_list = [multiply, add, subtract, divide]# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)llm_with_tools = llm.bind_tools(tools_list)# Nodesdef chat_model_node(state: State):system_message = "You are a helpful assistant that can use tools to answer questions. Once you have the result of a tool, provide a final answer without calling more tools."messages = [SystemMessage(content=system_message)] + state["messages"]return {"messages": [llm_with_tools.invoke(messages)]}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("chatbot_node", chat_model_node)tool_node = ToolNode(tools=tools_list)graph_builder.add_node("tools", tool_node)# Connecto nodesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_conditional_edges("chatbot_node", tools_condition)graph_builder.add_edge("tools", "chatbot_node")graph_builder.add_edge("chatbot_node", END)# Compile the graphgraph = graph_builder.compile(interrupt_before=["tools"], checkpointer=memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Como vemos no grafo, há um interrupt antes de usar as tools. Isso significa que vai parar antes de usá-las para nos pedir permissão
InputPython# Inputinitial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}config = {"configurable": {"thread_id": "1"}}# Run the graph until the first interruptionfor event in graph.stream(initial_input, config, stream_mode="updates"):if 'chatbot_node' in event:print(event['chatbot_node']['messages'][-1].pretty_print())else:print(event)Copied
================================== Ai Message ==================================[{'text': "I'll multiply 2 and 3 for you.", 'type': 'text'}, {'id': 'toolu_01QDuind1VBHWtvifELN9SPf', 'input': {'a': 2, 'b': 3}, 'name': 'multiply', 'type': 'tool_use'}]Tool Calls:multiply (toolu_01QDuind1VBHWtvifELN9SPf)Call ID: toolu_01QDuind1VBHWtvifELN9SPfArgs:a: 2b: 3None{'__interrupt__': ()}
Como podemos ver, o LLM sabe que tem que usar a ferramenta multiply, mas a execução é interrompida, porque ele tem que esperar que um humano autorize o uso da ferramenta.
Podemos ver o estado em que o grafo ficou
InputPythonstate = graph.get_state(config)state.nextCopied
('tools',)
Como vemos, ficou no nó de tools.
Podemos criar uma função (não no grafo, mas fora do grafo, para melhorar a experiência do usuário e que ele entenda por que a execução para) que peça ao usuário para aprovar o uso da ferramenta.
Criamos um novo thread_id para que seja criado um novo estado.
InputPython# Inputinitial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}config = {"configurable": {"thread_id": "2"}}# Run the graph until the first interruptionfor event in graph.stream(initial_input, config, stream_mode="updates"):function_name = Nonefunction_args = Noneif 'chatbot_node' in event:for element in event['chatbot_node']['messages'][-1].content:if element['type'] == 'text':print(element['text'])elif element['type'] == 'tool_use':function_name = element['name']function_args = element['input']print(f"The LLM wants to use the tool {function_name} with the arguments {function_args}")elif '__interrupt__' in event:passelse:print(event)question = f"Do you approve the use of the tool {function_name} with the arguments {function_args}? (y/n)"user_approval = input(question)print(f"{question}: {user_approval}")if user_approval.lower() == 'y':print("User approved the use of the tool")for event in graph.stream(None, config, stream_mode="updates"):if 'chatbot_node' in event:for element in event['chatbot_node']['messages'][-1].content:if isinstance(element, str):print(element, end="")elif 'tools' in event:result = event['tools']['messages'][-1].contenttool_used = event['tools']['messages'][-1].nameprint(f"The result of the tool {tool_used} is {result}")else:print(event)Copied
I'll multiply 2 and 3 for you.The LLM wants to use the tool multiply with the arguments {'a': 2, 'b': 3}Do you approve the use of the tool None with the arguments None? (y/n): yUser approved the use of the toolThe result of the tool multiply is 6The result of multiplying 2 and 3 is 6.
Podemos ver que nos perguntou se aprovamos o uso da tool de multiplicação, nós a aprovamos e o grafo terminou a execução. Vemos o estado do grafo.
InputPythonstate = graph.get_state(config)state.nextCopied
()
Vemos que o seguinte estado do grafo está vazio, isso indica que a execução do grafo foi concluída
Modificação do estado
Nota: Esta seção faremos usando Sonnet 3.7, já que, no momento da escrita deste post, é o melhor modelo para uso com agentes, e é o único que entende quando precisa chamar as tools e quando não para este exemplo
Vamos a repetir o exemplo anterior, mas em vez de interromper o grafo antes do uso de uma tool, vamos interrompê-lo no LLM; para isso, na hora de construir o grafo, indicamos que queremos pausá-lo no agente (graph_builder.compile(interrupt_before=["chatbot_node"], checkpointer=memory))
InputPythonfrom typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom langchain_core.messages import SystemMessage, HumanMessage, AIMessagefrom langgraph.prebuilt import ToolNode, tools_conditionfrom langgraph.checkpoint.memory import MemorySaverfrom langchain_core.tools import toolfrom langchain_anthropic import ChatAnthropicfrom IPython.display import Image, displayimport osimport dotenvdotenv.load_dotenv()ANTHROPIC_TOKEN = os.getenv("ANTHROPIC_LANGGRAPH_API_KEY")memory = MemorySaver()class State(TypedDict):messages: Annotated[list, add_messages]os.environ["LANGCHAIN_TRACING_V2"] = "false" # Disable LangSmith tracing# Tools@tooldef multiply(a: int, b: int) -> int:"""Multiply a and b.Args:a: first intb: second intReturns:The product of a and b."""return a * b@tooldef add(a: int, b: int) -> int:"""Adds a and b.Args:a: first intb: second intReturns:The sum of a and b."""return a + b@tooldef subtract(a: int, b: int) -> int:"""Subtract b from a.Args:a: first intb: second intReturns:The difference between a and b."""return a - b@tooldef divide(a: int, b: int) -> float:"""Divide a by b.Args:a: first intb: second intReturns:The quotient of a and b."""return a / btools_list = [multiply, add, subtract, divide]# Create the LLM modelllm = ChatAnthropic(model="claude-3-7-sonnet-20250219", api_key=ANTHROPIC_TOKEN)llm_with_tools = llm.bind_tools(tools_list)# Nodesdef chat_model_node(state: State):system_message = "You are a helpful assistant that can use tools to answer questions. Once you have the result of a tool, provide a final answer without calling more tools."messages = [SystemMessage(content=system_message)] + state["messages"]return {"messages": [llm_with_tools.invoke(messages)]}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("chatbot_node", chat_model_node)tool_node = ToolNode(tools=tools_list)graph_builder.add_node("tools", tool_node)# Connecto nodesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_conditional_edges("chatbot_node", tools_condition)graph_builder.add_edge("tools", "chatbot_node")graph_builder.add_edge("chatbot_node", END)# Compile the graphgraph = graph_builder.compile(interrupt_before=["chatbot_node"], checkpointer=memory)display(Image(graph.get_graph().draw_mermaid_png()))Copied
<IPython.core.display.Image object>
Vemos na representação do grafo que há um interrupt antes da execução de chatbot_node, então antes que o chatbot seja executado a execução será interrompida e teremos de ser nós a fazê-la continuar
Agora pedimos novamente uma multiplicação
InputPython# Inputinitial_input = {"messages": HumanMessage(content="Multiply 2 and 3")}config = {"configurable": {"thread_id": "1"}}# Run the graph until the first interruptionfor event in graph.stream(initial_input, config, stream_mode="updates"):if 'chatbot_node' in event:print(event['chatbot_node']['messages'][-1].pretty_print())else:print(event)Copied
{'__interrupt__': ()}
Podemos ver que não fez nada. Se observarmos o estado
InputPythonstate = graph.get_state(config)state.nextCopied
('chatbot_node',)
Vemos que o seguinte nó é o de chatbot. Além disso, se virmos os seus valores, vemos a mensagem que lhe enviámos
InputPythonstate.valuesCopied
{'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='08fd6084-ecd2-4156-ab24-00d2d5c26f00')]}
Agora vamos modificar o estado, adicionando uma nova mensagem
InputPythongraph.update_state(config,{"messages": [HumanMessage(content="No, actually multiply 3 and 3!")]})Copied
{'configurable': {'thread_id': '1','checkpoint_ns': '','checkpoint_id': '1f027eb6-6c8b-6b6a-8001-bc0f8942566c'}}
Obtemos o novo estado
InputPythonnew_state = graph.get_state(config)new_state.nextCopied
('chatbot_node',)
O seguinte nó continua sendo o do chatbot, mas se agora vemos as mensagens
InputPythonnew_state.valuesCopied
{'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='08fd6084-ecd2-4156-ab24-00d2d5c26f00'),HumanMessage(content='No, actually multiply 3 and 3!', additional_kwargs={}, response_metadata={}, id='e95394c2-e62e-47d2-b9b2-51eba40f3e22')]}
Vemos que o novo foi adicionado. Então fazemos com que a execução continue
InputPythonfor event in graph.stream(None, config, stream_mode="values"):event['messages'][-1].pretty_print()Copied
================================ Human Message =================================No, actually multiply 3 and 3!================================== Ai Message ==================================[{'text': "I'll multiply 3 and 3 for you.", 'type': 'text'}, {'id': 'toolu_01UABhLnEdg5ZqxVQTE5pGUx', 'input': {'a': 3, 'b': 3}, 'name': 'multiply', 'type': 'tool_use'}]Tool Calls:multiply (toolu_01UABhLnEdg5ZqxVQTE5pGUx)Call ID: toolu_01UABhLnEdg5ZqxVQTE5pGUxArgs:a: 3b: 3================================= Tool Message =================================Name: multiply9
Foi feita a multiplicação de 3 por 3, que é a modificação do estado que fizemos, e não 2 por 3, que é o que lhe pedimos da primeira vez
Isso pode ser útil quando temos um agente e queremos verificar se o que ele faz está correto, então podemos entrar na execução e modificar o estado
Pontos de interrupção dinâmicos
Até agora, criamos breakpoints estáticos por meio da compilação do grafo, mas podemos criar breakpoints dinâmicos usando NodeInterrupt. Isso é útil porque a execução pode ser interrompida por regras lógicas introduzidas por programação
Esses NodeInterrupt permitem personalizar como o usuário será notificado da interrupção
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 SystemMessage, HumanMessage, AIMessagefrom langgraph.checkpoint.memory import MemorySaverfrom langgraph.errors import NodeInterruptfrom 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]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 chatbot_function(state: State):max_len = 15input_message = state["messages"][-1]# Check len messageif len(input_message.content) > max_len:raise NodeInterrupt(f"Received input is longer than {max_len} characters --> {input_message}")# Invoke the LLM with the messagesresponse = llm.invoke(state["messages"])# Return the LLM's response in the correct state formatreturn {"messages": [response]}# Create graph buildergraph_builder = StateGraph(State)# Add nodesgraph_builder.add_node("chatbot_node", chatbot_function)# Connecto nodesgraph_builder.add_edge(START, "chatbot_node")graph_builder.add_edge("chatbot_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>
Como se pode ver, criamos uma interrupção caso a mensagem seja longa. Vamos testá-lo
InputPythoninitial_input = {"messages": HumanMessage(content="Hello, how are you? My name is Máximo")}config = {"configurable": {"thread_id": "1"}}# Run the graph until the first interruptionfor event in graph.stream(initial_input, config, stream_mode="updates"):if 'chatbot_node' in event:print(event['chatbot_node']['messages'][-1].pretty_print())else:print(event)Copied
{'__interrupt__': (Interrupt(value="Received input is longer than 15 characters --> content='Hello, how are you? My name is Máximo' additional_kwargs={} response_metadata={} id='2bdc6d41-0cfe-4d3c-8748-ca7d46fd5a60'", resumable=False, ns=None),)}
De fato, a interrupção foi interrompida e nos deu a mensagem de erro que criamos
Se vemos o nó em que ele parou
InputPythonstate = graph.get_state(config)state.nextCopied
('chatbot_node',)
Vemos que está parado no nó do chatbot. Podemos fazer com que ele continue a execução novamente, mas ele vai nos dar o mesmo erro
InputPythonfor event in graph.stream(None, config, stream_mode="updates"):if 'chatbot_node' in event:print(event['chatbot_node']['messages'][-1].pretty_print())else:print(event)Copied
{'__interrupt__': (Interrupt(value="Received input is longer than 15 characters --> content='Hello, how are you? My name is Máximo' additional_kwargs={} response_metadata={} id='2bdc6d41-0cfe-4d3c-8748-ca7d46fd5a60'", resumable=False, ns=None),)}
Então temos que modificar o estado
InputPythongraph.update_state(config,{"messages": [HumanMessage(content="How are you?")]})Copied
{'configurable': {'thread_id': '1','checkpoint_ns': '','checkpoint_id': '1f027f13-5827-6a18-8001-4209d5a866f0'}}
Voltamos a ver o estado e seus valores
InputPythonnew_state = graph.get_state(config)print(f"Siguiente nodo: {new_state.next}")print("Valores:")for value in new_state.values["messages"]:print(f" {value.content}")Copied
Siguiente nodo: ('chatbot_node',)Valores:Hello, how are you? My name is MáximoHow are you?
A última mensagem é mais curta, por isso tentamos retomar a execução do grafo
InputPythonfor event in graph.stream(None, config, stream_mode="updates"):if 'chatbot_node' in event:print(event['chatbot_node']['messages'][-1].pretty_print())else:print(event)Copied
================================== Ai Message ==================================Hello Máximo! I'm doing well, thank you for asking. How about you? How can I assist you today?None
---
➡️ **Continua na Parte 4: personalização do estado e checkpoints**.