You can integrate Atlas Vector Search with LangGraph to build AI agents. This tutorial demonstrates how to build a simple agent with LangGraph that answers questions about some sample data in Atlas. You can use the code in this tutorial as a starting point to build more complex AI agents.
Specifically, you perform the following actions:
Set up the environment.
Use Atlas as a vector database.
Define tools for the agent.
Build and run the graph.
Add memory to the agent.
Work with a runnable version of this tutorial as a Python notebook.
Prerequisites
To complete this tutorial, you must have the following:
An Atlas account with a cluster running MongoDB version 6.0.11, 7.0.2, or later (including RCs). Ensure that your IP address is included in your Atlas project's access list. To learn more, see Create a Cluster.
A Voyage AI API Key. To create an account and API Key, see the Voyage AI website.
An OpenAI API Key. You must have an OpenAI account with credits available for API requests. To learn more about registering an OpenAI account, see the OpenAI API website.
An environment to run interactive Python notebooks such as Colab.
Set Up the Environment
Set up the environment for this tutorial.
Create an interactive Python notebook by saving a file
with the .ipynb extension. This notebook allows you to
run Python code snippets individually, and you'll use
it to run the code in this tutorial.
To set up your notebook environment:
Set environment variables.
Run the following code to set the environment variables for this tutorial. Provide your API keys and Atlas cluster's SRV connection string.
import os os.environ["VOYAGE_API_KEY"] = "<voyage-api-key>" os.environ["OPENAI_API_KEY"] = "<openai-api-key>" MONGODB_URI = "<connection-string>"
Note
Your connection string should use the following format:
mongodb+srv://<db_username>:<db_password>@<clusterName>.<hostname>.mongodb.net
Use Atlas as a Vector Database
You will use Atlas as the vector database to store and retrieve documents for the agent. To quickly start using Atlas as a vector database:
Load the sample data.
For this tutorial, you use one of our sample datasets as the data source, so you can start building the agent's workflow right away. If you haven't already, complete the steps to load sample data into your Atlas cluster.
Specifically, you will use the embedded_movies dataset, which contains documents about movies, including the vector embeddings of their plots.
Note
If you want to use your own data, see LangChain Get Started or How to Create Vector Embeddings to learn how to ingest vector embeddings into Atlas.
Instantiate the vector store.
In your notebook, paste the following code to configure Atlas as a vector database by using the LangChain integration. Specifically, the code instantiates a vector store object that you can use to interact with Atlas as a vector database. It specifies the following:
The
sample_mflix.embedded_moviesnamespace as the data source that contains the vector embeddings and text data.Voyage AI's
voyage-3-largeembedding model as the model used to convert text into embeddings during queries.plotas the field in the collection that contains the text.plot_embedding_voyage_3_largeas the field in the collection that contains the embeddings.dotProductas the relevance score function to use for vector search.
from langchain_mongodb import MongoDBAtlasVectorSearch from langchain_voyageai import VoyageAIEmbeddings embedding_model = VoyageAIEmbeddings( model = "voyage-3-large", output_dimension = 2048 ) # Instantiate the vector store vector_store = MongoDBAtlasVectorSearch.from_connection_string( connection_string = MONGODB_URI, namespace = "sample_mflix.embedded_movies", embedding = embedding_model, text_key = "plot", embedding_key = "plot_embedding_voyage_3_large", relevance_score_fn = "dotProduct" )
Create the indexes.
Note
To create an Atlas Vector Search index, you must have Project Data Access Admin
or higher access to the Atlas project.
To enable vector search and full-text search queries on your data in Atlas, create an Atlas Vector Search and Atlas Search index on the collection. You can create the indexes by using either the LangChain helper methods or the PyMongo Driver method:
Create the Atlas Vector Search index.
Run the following code to create a vector search index that indexes the
plot_embedding_voyage_3_largefield in the collection.# Use helper method to create the vector search index vector_store.create_vector_search_index( dimensions = 2048 ) Create the Atlas Search index.
Run the following code in your notebook to create a search index that indexes the
titlefield in the collection.from langchain_mongodb.index import create_fulltext_search_index from pymongo import MongoClient # Connect to your cluster client = MongoClient(MONGODB_URI) collection = client["sample_mflix"]["embedded_movies"] # Use helper method to create the search index create_fulltext_search_index( collection = collection, field = "title", index_name = "search_index" )
Create the Atlas Vector Search index.
Run the following code to create a vector search index that indexes the
plot_embedding_voyage_3_largefield in the collection.from pymongo.operations import SearchIndexModel # Connect to your cluster client = MongoClient(MONGODB_URI) collection = client["sample_mflix"]["embedded_movies"] # Create your vector search index model, then create the index vector_index_model = SearchIndexModel( definition={ "fields": [ { "type": "vector", "path": "plot_embedding_voyage_3_large", "numDimensions": 2048, "similarity": "dotProduct" } ] }, name="vector_index", type="vectorSearch" ) collection.create_search_index(model=vector_index_model) Create the Atlas Search index.
Run the following code to create a search index that indexes the
titlefield in the collection.1 # Create your search index model, then create the search index 2 search_index_model = SearchIndexModel( 3 definition={ 4 "mappings": { 5 "dynamic": False, 6 "fields": { 7 "plot": { 8 "type": "title" 9 } 10 } 11 } 12 }, 13 name="search_index" 14 ) 15 collection.create_search_index(model=search_index_model)
The indexes should take about one minute to build. While they build, the indexes are in an initial sync state. When they finish building, you can start querying the data in your collection.
Define Agent Tools
In this section, you define tools that the agent can use to perform specific tasks, and then bind these tools to the LLM. You define the following tools:
Vector search tool to retrieve movies that are semantically similar to the user query.
Full-text search tool to find a specific movie title and retrieve its plot.
Paste and run the following code in your notebook to define and test the tools:
Define a tool for vector search.
This tool uses the vector store object as a retriever. Under the hood, the retriever runs an Atlas Vector Search query to retrieve semantically similar documents. The tool then returns the titles and plots of the retrieved movie documents.
from langchain.agents import tool # Define a vector search tool def vector_search(user_query: str) -> str: """ Retrieve information using vector search to answer a user query. """ retriever = vector_store.as_retriever( search_type = "similarity", search_kwargs = { "k": 5 } # Retrieve top 5 most similar documents ) results = retriever.invoke(user_query) # Concatenate the results into a string context = "\n\n".join([f"{doc.metadata['title']}: {doc.page_content}" for doc in results]) return context # Test the tool test_results = vector_search.invoke("What are some movies that take place in the ocean?") print(test_results)
20,000 Leagues Under the Sea: In the 19th century, an expert marine biologist is hired by the government to determine what's sinking ships all over the ocean. His daughter follows him. They are intercepted by a mysterious captain Nemo and his incredible submarine. Deep Rising: A group of heavily armed hijackers board a luxury ocean liner in the South Pacific Ocean to loot it, only to do battle with a series of large-sized, tentacled, man-eating sea creatures who have taken over the ship first. The Rift: An experimental submarine, the "Siren II", with a very experienced crew is sent to find out what happened to the "Siren I", mysteriously disappeared in a submarine rift. Things go awry when... Heaven Knows, Mr. Allison: A Marine and a Nun, The Marine is shipwrecked on a Pacific Island and the Nun has been left behind there, they find comfort in one another as the two wait out the war. Waterworld: In a future where the polar ice-caps have melted and Earth is almost entirely submerged, a mutated mariner fights starvation and outlaw "smokers," and reluctantly helps a woman and a young girl try to find dry land.
Define a tool for full-text search.
This tool uses the full-text search retriever to retrieve movie documents that match the specified movie title. Then, the tool returns the plot of the specified movie.
from langchain_mongodb.retrievers.full_text_search import MongoDBAtlasFullTextSearchRetriever # Define a full-text search tool def full_text_search(user_query: str) -> str: """ Retrieve movie plot content based on the provided title. """ # Initialize the retriever retriever = MongoDBAtlasFullTextSearchRetriever( collection = collection, # MongoDB Collection in Atlas search_field = "title", # Name of the field to search search_index_name = "search_index", # Name of the search index top_k = 1, # Number of top results to return ) results = retriever.invoke(user_query) for doc in results: if doc: return doc.metadata["fullplot"] else: return "Movie not found" # Test the tool full_text_search.invoke("What is the plot of Titanic?")
"The plot focuses on the romances of two couples upon the doomed ship's maiden voyage. Isabella Paradine (Catherine Zeta-Jones) is a wealthy woman mourning the loss of her aunt, who reignites a romance with former flame Wynn Park (Peter Gallagher). Meanwhile, a charming ne'er-do-well named Jamie Perse (Mike Doyle) steals a ticket for the ship, and falls for a sweet innocent Irish girl on board. But their romance is threatened by the villainous Simon Doonan (Tim Curry), who has discovered about the ticket and makes Jamie his unwilling accomplice, as well as having sinister plans for the girl."
Prepare the LLM.
The following code prepares the LLM for the agent by doing the following:
Specifies which LLM to use. By default, the
ChatOpenAIclass usesgpt-3.5-turbo.Defines a LangChain prompt template to instruct the LLM on how to generate responses, including how to handle tool calls.
Binds the tools and prompt template to the LLM.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI # Initialize the LLM llm = ChatOpenAI() # Create a chat prompt template for the agent, which includes a system prompt and a placeholder for `messages` prompt = ChatPromptTemplate.from_messages( [ ( "You are a helpful AI agent." " You are provided with tools to answer questions about movies." " Think step-by-step and use these tools to get the information required to answer the user query." " Do not re-run tools unless absolutely necessary." " If you are not able to get enough information using the tools, reply with I DON'T KNOW." " You have access to the following tools: {tool_names}." ), MessagesPlaceholder(variable_name="messages"), ] ) tools = [ vector_search, full_text_search ] # Provide the tool names to the prompt prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools])) # Prepare the LLM by making the tools and prompt available to the model bind_tools = llm.bind_tools(tools) llm_with_tools = prompt | bind_tools
Test the tool calls.
You can run the following code snippets to test that the LLM makes the correct tool calls based on the query by checking the name of the tool that the LLM is calling:
# Here, we expect the LLM to use the 'vector_search' tool. llm_with_tools.invoke(["What are some movies that take place in the ocean?"]).tool_calls
[{'name': 'vector_search', 'args': {'user_query': 'movies that take place in the ocean'}, 'id': 'call_gBrzDrB35i3bafwWMt5YJQ3E', 'type': 'tool_call'}]
# Here, we expect the LLM to use the 'full_text_search' tool. llm_with_tools.invoke(["What's the plot of Titanic?"]).tool_calls
[{'name': 'full_text_search', 'args': {'user_query': 'Titanic'}, 'id': 'call_rxrOG8DuHWzhVvaai7NHMNTU', 'type': 'tool_call'}]
Note
You can define any tool that you need to perform a specific task. You can also define tools for other retrieval methods, such as hybrid search or parent-document retrieval.
Build the Graph
In this section, you build a graph to orchestrate the agent's workflow. The graph defines the sequence of steps that the agent takes to respond to a query.
This agent uses the following workflow:
The agent receives a user query.
In the agent node, the tool-bound LLM generates a response based on the query.
This response includes information about whether the agent should use a tool. If the agent determines a tool is needed, it adds the tool configuration to the graph state and proceeds to the tools node. Otherwise, the agent generates an answer directly without using a tool.
If the response indicates a tool is needed, the workflow continues to the tools node.
In this node, the agent reads the tool configuration from the graph state.
Back in the agent node, the LLM receives the retrieved context and generates a final response.
Paste and run the following code in your notebook to build and run the graph:
Define the graph state.
The graph state
maintains the state of the graph throughout the workflow. It can contain any shared data
that needs to be tracked and modified across different nodes. In this example,
the GraphState component uses a dictionary that tracks the agent's messages,
which includes the user query, the LLM's responses, and the results of tool calls.
However, you can customize your graph state to include any data relevant to your application.
from typing import Annotated from typing_extensions import TypedDict from langgraph.graph import StateGraph, START from langgraph.graph.message import add_messages # Define the graph state class GraphState(TypedDict): messages: Annotated[list, add_messages] # Instantiate the graph graph = StateGraph(GraphState)
Define the nodes.
For this agent, you define two custom nodes :
Add the agent node.
This node processes the messages in the current state, invokes the LLM with these messages, and updates the state with the LLM's response, which includes any tool calls.
from typing import Dict, List # Define the agent node function def agent(state: GraphState) -> Dict[str, List]: """ Agent node Args: state (GraphState): Graph state Returns: Dict[str, List]: Updates to messages """ # Get the messages from the graph `state` messages = state["messages"] # Invoke `llm_with_tools` with `messages` result = llm_with_tools.invoke(messages) # Write `result` to the `messages` attribute of the graph state return {"messages": [result]} # Add "agent" node using the `add_node` function graph.add_node("agent", agent) Add the tools node.
This node processes tool calls, determines the appropriate tool to use based on the current state, and updates the message history with the results of the tool call.
from langchain_core.messages import ToolMessage # Create a map of tool name to tool call tools_by_name = {tool.name: tool for tool in tools} # Define the tools node function def tools_node(state: GraphState) -> Dict[str, List]: result = [] # Get the list of tool calls from messages tool_calls = state["messages"][-1].tool_calls # Iterate through `tool_calls` for tool_call in tool_calls: # Get the tool from `tools_by_name` using the `name` attribute of the `tool_call` tool = tools_by_name[tool_call["name"]] # Invoke the `tool` using the `args` attribute of the `tool_call` observation = tool.invoke(tool_call["args"]) # Append the result of executing the tool to the `result` list as a ToolMessage result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"])) # Write `result` to the `messages` attribute of the graph state return {"messages": result} # Add "tools" node using the `add_node` function graph.add_node("tools", tools_node)
Define the edges.
Edges connect the nodes in the graph and define the flow of the agent. In this code, you define the following edges:
The following normal edges that route:
Start node to agent node.
Agent node to tools node.
A conditional edge that routes the tools node to the agent node if the state contains tool calls. Otherwise, routes to the end node.
from langgraph.graph import END # Add an edge from the START node to the `agent` node graph.add_edge(START, "agent") # Add an edge from the `tools` node to the `agent` node graph.add_edge("tools", "agent") # Define a conditional edge def route_tools(state: GraphState): """ Uses a conditional_edge to route to the tools node if the last message has tool calls. Otherwise, route to the end. """ # Get messages from graph state messages = state.get("messages", []) if len(messages) > 0: # Get the last AI message from messages ai_message = messages[-1] else: raise ValueError(f"No messages found in input state to tool_edge: {state}") # Check if the last message has tool calls if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0: return "tools" return END # Add a conditional edge from the `agent` node to the `tools` node graph.add_conditional_edges( "agent", route_tools, {"tools": "tools", END: END}, )
Define the execution function.
Define the execution function to run the agent's workflow. The following execution function streams outputs from the graph as they progress through the nodes, so you can see the agent's outputs in real time:
# Stream outputs from the graph as they pass through its nodes def execute_graph(user_input: str) -> None: # Add user input to the messages attribute of the graph state input = {"messages": [("user", user_input)]} # Pass input to the graph and stream the outputs for output in app.stream(input): for key, value in output.items(): print(f"Node {key}:") print(value) print("\n---FINAL ANSWER---") print(value["messages"][-1].content)
Test the agent.
Run the following sample queries to test the agent. Your generated responses might vary.
execute_graph("What are some movies that take place in the ocean?")
Node agent: {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ki3W2Ouw1ziAhtAKKojpHaUE', 'function': {'arguments': '{"user_query":"movies that take place in the ocean"}', 'name': 'vector_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 170, 'total_tokens': 192, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-11-20', 'system_fingerprint': 'fp_ee1d74bde0', 'id': 'chatcmpl-BtFYLHDXaWztcDYqZJhfarlB5Tmqy', 'service_tier': None, 'prompt_filter_results': [{'prompt_index': 0, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'jailbreak': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}], 'finish_reason': 'tool_calls', 'logprobs': None, 'content_filter_results': {}}, id='run--2da5bc48-6a4a-4f45-938b-b5f77036958a-0', tool_calls=[{'name': 'vector_search', 'args': {'user_query': 'movies that take place in the ocean'}, 'id': 'call_ki3W2Ouw1ziAhtAKKojpHaUE', 'type': 'tool_call'}], usage_metadata={'input_tokens': 170, 'output_tokens': 22, 'total_tokens': 192, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} Node tools: {'messages': [ToolMessage(content='20,000 Leagues Under the Sea: In the 19th century, an expert marine biologist is hired by the government to determine what\'s sinking ships all over the ocean. His daughter follows him. They are intercepted by a mysterious captain Nemo and his incredible submarine.\n\nDeep Rising: A group of heavily armed hijackers board a luxury ocean liner in the South Pacific Ocean to loot it, only to do battle with a series of large-sized, tentacled, man-eating sea creatures who have taken over the ship first.\n\nWaterworld: In a future where the polar ice-caps have melted and Earth is almost entirely submerged, a mutated mariner fights starvation and outlaw "smokers," and reluctantly helps a woman and a young girl try to find dry land.\n\nThe Rift: An experimental submarine, the "Siren II", with a very experienced crew is sent to find out what happened to the "Siren I", mysteriously disappeared in a submarine rift. Things go awry when...\n\nHeaven Knows, Mr. Allison: A Marine and a Nun, The Marine is shipwrecked on a Pacific Island and the Nun has been left behind there, they find comfort in one another as the two wait out the war.', id='ece7a906-a8a5-4bc4-8ea7-927d66f8f458', tool_call_id='call_ki3W2Ouw1ziAhtAKKojpHaUE')]} Node agent: {'messages': [AIMessage(content='Here are some movies that take place in the ocean:\n\n1. **20,000 Leagues Under the Sea** - This movie is about a marine biologist and his daughter who investigate sinking ships and are intercepted by Captain Nemo and his advanced submarine.\n\n2. **Deep Rising** - A group of heavily armed hijackers board a luxury ocean liner in the South Pacific, only to face off against man-eating sea creatures.\n\n3. **Waterworld** - Set in a future where the Earth is almost entirely submerged due to melted polar ice caps, the story follows a mutated mariner who reluctantly helps others in their search for dry land.\n\n4. **The Rift** - Focuses on an experimental submarine named "Siren II" and its crew, who explore a mysterious submarine rift to find out what happened to the "Siren I".\n\n5. **Heaven Knows, Mr. Allison** - A story of a Marine and a nun who are stranded on a Pacific Island and develop a bond as they wait out the war.\n\nWould you like a deeper dive into any of these movies?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 223, 'prompt_tokens': 449, 'total_tokens': 672, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-11-20', 'system_fingerprint': 'fp_ee1d74bde0', 'id': 'chatcmpl-BtFYNztmx5TbkU5wtpDXl5pK5BRRz', 'service_tier': None, 'prompt_filter_results': [{'prompt_index': 0, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'jailbreak': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}], 'finish_reason': 'stop', 'logprobs': None, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'protected_material_code': {'filtered': False, 'detected': False}, 'protected_material_text': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}, id='run--49c853a7-6d15-4468-a3a0-52f8610e9895-0', usage_metadata={'input_tokens': 449, 'output_tokens': 223, 'total_tokens': 672, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- Here are some movies that take place in the ocean: 1. **20,000 Leagues Under the Sea** - This movie is about a marine biologist and his daughter who investigate sinking ships and are intercepted by Captain Nemo and his advanced submarine. 2. **Deep Rising** - A group of heavily armed hijackers board a luxury ocean liner in the South Pacific, only to face off against man-eating sea creatures. 3. **Waterworld** - Set in a future where the Earth is almost entirely submerged due to melted polar ice caps, the story follows a mutated mariner who reluctantly helps others in their search for dry land. 4. **The Rift** - Focuses on an experimental submarine named "Siren II" and its crew, who explore a mysterious submarine rift to find out what happened to the "Siren I". 5. **Heaven Knows, Mr. Allison** - A story of a Marine and a nun who are stranded on a Pacific Island and develop a bond as they wait out the war. Would you like a deeper dive into any of these movies?
execute_graph("What's the plot of Titanic?")
Node agent: {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_FovVlygLymvbxDzNeEfQGedG', 'function': {'arguments': '{"user_query":"Titanic"}', 'name': 'full_text_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 237, 'total_tokens': 255, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-6ee12cbd-0c4a-451f-b56b-83851359d0bb-0', tool_calls=[{'name': 'full_text_search', 'args': {'user_query': 'Titanic'}, 'id': 'call_FovVlygLymvbxDzNeEfQGedG', 'type': 'tool_call'}], usage_metadata={'input_tokens': 237, 'output_tokens': 18, 'total_tokens': 255, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} Node tools: {'messages': [ToolMessage(content="The plot focuses on the romances of two couples upon the doomed ship's maiden voyage. Isabella Paradine (Catherine Zeta-Jones) is a wealthy woman mourning the loss of her aunt, who reignites a romance with former flame Wynn Park (Peter Gallagher). Meanwhile, a charming ne'er-do-well named Jamie Perse (Mike Doyle) steals a ticket for the ship, and falls for a sweet innocent Irish girl on board. But their romance is threatened by the villainous Simon Doonan (Tim Curry), who has discovered about the ticket and makes Jamie his unwilling accomplice, as well as having sinister plans for the girl.", id='2cd41281-d195-44af-9ae1-f3ff099194a9', tool_call_id='call_FovVlygLymvbxDzNeEfQGedG')]} Node agent: {'messages': [AIMessage(content='The plot of "Titanic" focuses on the romances of two couples on the doomed ship\'s maiden voyage. Isabella Paradine, a wealthy woman, reunites with her former flame, Wynn Park. Meanwhile, a charming ne\'er-do-well named Jamie Perse falls for an innocent Irish girl on board, but their romance is threatened by a villainous character named Simon Doonan.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 83, 'prompt_tokens': 395, 'total_tokens': 478, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-cdf65abe-7ce0-417a-8f5b-84989521f47e-0', usage_metadata={'input_tokens': 395, 'output_tokens': 83, 'total_tokens': 478, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- The plot of "Titanic" focuses on the romances of two couples on the doomed ship's maiden voyage. Isabella Paradine, a wealthy woman, reunites with her former flame, Wynn Park. Meanwhile, a charming ne'er-do-well named Jamie Perse falls for an innocent Irish girl on board, but their romance is threatened by a villainous character named Simon Doonan.
Add Memory
To improve the agent's performance, you can persist its state. Persistence allows the agent to store information about previous interactions, which the agent can use in future interactions to provide more contextually relevant responses.
Initialize the checkpointer.
To persist
the state of the graph execution, you can use checkpointers to save
the state to a specific thread, which can be accessed even
after the graph execution ends.
The MongoDBSaver checkpointer allows you to use MongoDB as the
backing database for persisting the checkpoint state. Run the following code
in your notebook to initialize the checkpointer and use it in your graph:
from langgraph.checkpoint.mongodb import MongoDBSaver # Initialize a MongoDB checkpointer checkpointer = MongoDBSaver(client) # Instantiate the graph with the checkpointer app = graph.compile(checkpointer=checkpointer)
Update the execution function.
You must also update the execution function to reference
the thread_id, which is the unique identifier for the
thread that you want to persist. Run the following code in your
notebook to update the execution function:
# Update the `execute_graph` function to include the `thread_id` argument def execute_graph(thread_id: str, user_input: str) -> None: config = {"configurable": {"thread_id": thread_id}} input = { "messages": [ ( "user", user_input, ) ] } for output in app.stream(input, config): for key, value in output.items(): print(f"Node {key}:") print(value) print("\n---FINAL ANSWER---") print(value["messages"][-1].content)
Test the agent.
Run the following code snippets in your notebook to test the agent. Your generated responses might vary.
The first code snippet runs a query and saves the response to a thread with the specified
thread_idof1.The second code snippet runs a query about the previous interaction, loading the state from the thread with the specified
thread_idof1. It generates a response that is aware of the previous interaction.
execute_graph("1", "What's the plot of Titanic?")
Node agent: {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ZgBYIdPqV720s3oN7TC61Sjn', 'function': {'arguments': '{"user_query":"Titanic"}', 'name': 'full_text_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 860, 'total_tokens': 878, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-91a84f0d-ddba-4753-8de6-6db1d059f238-0', tool_calls=[{'name': 'full_text_search', 'args': {'user_query': 'Titanic'}, 'id': 'call_ZgBYIdPqV720s3oN7TC61Sjn', 'type': 'tool_call'}], usage_metadata={'input_tokens': 860, 'output_tokens': 18, 'total_tokens': 878, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} Node tools: {'messages': [ToolMessage(content="The plot focuses on the romances of two couples upon the doomed ship's maiden voyage. Isabella Paradine (Catherine Zeta-Jones) is a wealthy woman mourning the loss of her aunt, who reignites a romance with former flame Wynn Park (Peter Gallagher). Meanwhile, a charming ne'er-do-well named Jamie Perse (Mike Doyle) steals a ticket for the ship, and falls for a sweet innocent Irish girl on board. But their romance is threatened by the villainous Simon Doonan (Tim Curry), who has discovered about the ticket and makes Jamie his unwilling accomplice, as well as having sinister plans for the girl.", id='20507bc4-383f-4478-8ffc-9386e423509c', tool_call_id='call_ZgBYIdPqV720s3oN7TC61Sjn')]} Node agent: {'messages': [AIMessage(content='The plot of "Titanic" focuses on the romances of two couples aboard the doomed ship\'s maiden voyage. It tells the story of Isabella Paradine, who rekindles a romance with Wynn Park, and Jamie Perse, who falls in love with an Irish girl on board. Their romances are jeopardized by the villainous Simon Doonan\'s sinister plans.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 80, 'prompt_tokens': 1018, 'total_tokens': 1098, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-8b1916d2-b5b4-4d17-be04-589a701e17dc-0', usage_metadata={'input_tokens': 1018, 'output_tokens': 80, 'total_tokens': 1098, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- The plot of "Titanic" focuses on the romances of two couples aboard the doomed ship's maiden voyage. It tells the story of Isabella Paradine, who rekindles a romance with Wynn Park, and Jamie Perse, who falls in love with an Irish girl on board. Their romances are jeopardized by the villainous Simon Doonan's sinister plans.
execute_graph("1", "What movies are similar to the one I just asked about?")
Node agent: {'messages': [AIMessage(content='Here are some movies that share similarities with "Titanic" in terms of themes or setting:\n\n1. **Poseidon** - On New Year\'s Eve, the luxury ocean liner Poseidon capsizes after being hit by a rogue wave. The survivors struggle to escape the sinking ship. Like "Titanic," it involves a disaster on a luxury liner.\n\n2. **Raise the Titanic** - This movie focuses on a mission to raise the Titanic to recover a rare mineral. While the story is different, it still revolves around the famous Titanic ship.\n\n3. **The Poseidon Adventure** - A group of passengers fights to survive after their ocean liner completely capsizes at sea. It’s another disaster movie set on a ship, much like "Titanic."\n\n4. **After the Storm** - Following the sinking of a luxury yacht in a violent storm, greed leads to betrayal among survivors. This film also involves a maritime disaster and the human drama that unfolds.\n\nThese movies share overlapping themes of survival, maritime catastrophe, and human relationships in the face of tragedy.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 214, 'prompt_tokens': 467, 'total_tokens': 681, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-11-20', 'system_fingerprint': 'fp_ee1d74bde0', 'id': 'chatcmpl-BtFZMLYcrGfPKQVU4QMU9cR3GltIw', 'service_tier': None, 'prompt_filter_results': [{'prompt_index': 0, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'jailbreak': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}], 'finish_reason': 'stop', 'logprobs': None, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'protected_material_code': {'filtered': False, 'detected': False}, 'protected_material_text': {'filtered': False, 'detected': False}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}, id='run--97e9e332-3474-4635-9618-cb2e24536f83-0', usage_metadata={'input_tokens': 467, 'output_tokens': 214, 'total_tokens': 681, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- Here are some movies that share similarities with "Titanic" in terms of themes or setting: 1. **Poseidon** - On New Year's Eve, the luxury ocean liner Poseidon capsizes after being hit by a rogue wave. The survivors struggle to escape the sinking ship. Like "Titanic," it involves a disaster on a luxury liner. 2. **Raise the Titanic** - This movie focuses on a mission to raise the Titanic to recover a rare mineral. While the story is different, it still revolves around the famous Titanic ship. 3. **The Poseidon Adventure** - A group of passengers fights to survive after their ocean liner completely capsizes at sea. It’s another disaster movie set on a ship, much like "Titanic." 4. **After the Storm** - Following the sinking of a luxury yacht in a violent storm, greed leads to betrayal among survivors. This film also involves a maritime disaster and the human drama that unfolds. These movies share overlapping themes of survival, maritime catastrophe, and human relationships in the face of tragedy.