使用 LangGraph 和 Atlas Vector Search 构建 AI 代理
您可以将 Atlas Vector Search 与 LangGraph 集成,以构建 AI 代理。本教程演示了如何使用 LangGraph 构建一个简单的代理,回答有关 Atlas 中某些示例数据的问题。您可以使用本教程中的代码作为起点,构建更复杂的 AI 代理。
具体来说,您需要执行以下操作:
设置环境。
将 Atlas 作为矢量数据库。
为代理定义工具。
构建并运行图表。
为代理添加内存。
使用本教程的可运行版本以作为 Python 笔记本。
先决条件
如要完成本教程,您必须具备以下条件:
一个 Atlas 帐户,而其集群运行着 MongoDB 版本 6.0.11、7.0.2 或更高版本(包括 RC)。确保您的 IP 地址包含在 Atlas 项目的访问列表中。如需了解详情,请参阅创建集群。
一个 OpenAI API 密钥。您必须拥有一个 OpenAI 账号,该账号具有可用于 API 请求的信用额度。要了解有关注册 OpenAI 账号的更多信息,请参阅 OpenAI API 网站。
运行交互式 Python 笔记本(例如 Colab)的环境。
设置环境
为此教程设置环境。 通过保存具有 .ipynb
扩展名的文件来创建交互式Python笔记本。 此 Notebook 允许您单独运行Python代码片段,并且您将使用它来运行本教程中的代码。
要设立笔记本环境,请执行以下操作:
设置环境变量。
运行以下代码为本教程设立环境变量。提供 OpenAI API密钥和Atlas集群的 SRV 连接字符串。
import os os.environ["OPENAI_API_KEY"] = "<api-key>" MONGODB_URI = "<connection-string>"
注意
连接字符串应使用以下格式:
mongodb+srv://<db_username>:<db_password>@<clusterName>.<hostname>.mongodb.net
将 Atlas 作为矢量数据库
您将 Atlas 作为矢量数据库为代理存储和检索文档。要快速开始将 Atlas 作为矢量数据库使用:
加载示例数据。
在本教程中,您将使用我们的一个示例数据集作为数据源,从而可以立即开始构建代理的工作流程。如果您还没有完成,请完成将示例数据加载到 Atlas 集群的步骤。
具体而言,您将使用 embedded_movies 数据集,该数据集包含有关电影的文档,包括其情节的向量嵌入。
注意
如果您想使用自己的数据,请参阅 LangChain 入门或如何创建向量嵌入,以了解如何将向量嵌入导入 Atlas。
实例化向量存储。
在您的笔记本中,粘贴以下代码,通过 LangChain 集成将 Atlas 配置为矢量数据库。具体来说,代码实例化了一个矢量存储对象,您可以用它作为矢量数据库与 Atlas 进行交互。它指定了以下内容:
作为包含向量嵌入和文本数据的数据源的
sample_mflix.embedded_movies
命名空间。OpenAI 的
text-embedding-ada-002
嵌入模型是用于在查询过程中将文本转换为嵌入的模型。plot
作为集合中包含文本的字段。plot_embedding
作为集合中包含嵌入的字段。dotProduct
作为用于向量搜索的相关性评分函数。
from langchain_mongodb import MongoDBAtlasVectorSearch from langchain_openai import OpenAIEmbeddings embedding_model = OpenAIEmbeddings(model="text-embedding-ada-002", disallowed_special=()) # 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", relevance_score_fn = "dotProduct" )
创建索引。
注意
要创建 Atlas Vector Search 索引,您必须对 Atlas 项目具有Project Data Access Admin
或更高访问权限。
要在 Atlas 中启用向量搜索和全文搜索查询,请在集合上创建 Atlas Vector Search 和 Atlas Search 索引。您可以使用 LangChain 辅助方法或 PyMongo 驱动程序方法来创建索引:
创建Atlas Search索引。
在笔记本中运行以下代码以创建搜索索引,为集合中的 title
字段建立索引。
from langchain_mongodb.index import create_fulltext_search_index from pymongo import MongoClient # Connect to your cluster client = MongoClient(MONGODB_URI) # Use helper method to create the search index create_fulltext_search_index( collection = client["sample_mflix"]["embedded_movies"], field = "title", index_name = "search_index" )
创建 Atlas Vector Search 索引。
运行以下代码以创建向量搜索索引,为集合中的plot_embedding
字段编制索引。
from pymongo.operations import SearchIndexModel # Create your vector search index model, then create the index vector_index_model = SearchIndexModel( definition={ "fields": [ { "type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "dotProduct" } ] }, name="vector_index", type="vectorSearch" ) collection.create_search_index(model=vector_index_model)
创建Atlas Search索引。
运行以下代码以创建搜索索引,为集合中的title
字段编制索引。
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)
构建索引大约需要一分钟时间。在构建时,索引处于初始同步状态。构建完成后,您可以开始查询集合中的数据。
定义代理工具
在本节中,您将定义代理可用于执行特定任务的工具,然后将这些工具绑定到 LLM。您定义以下工具:
向量搜索工具,用于检索与用户查询在语义上相似的电影。
全文搜索工具,用于查找特定电影标题并检索其情节。
将笔记本中粘贴和运行以下代码,以定义和测试工具:
定义一个用于向量搜索的工具。
此工具使用向量存储对象作为检索器。在后台,检索器运行 Atlas Vector Search 查询,以检索语义相似的文档。然后,该工具返回检索到的电影文档的标题和情节。
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. Lost River: A single mother is swept into a dark underworld, while her teenage son discovers a road that leads him to a secret underwater town. 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. Poseidon: On New Year's Eve, the luxury ocean liner Poseidon capsizes after being swamped by a rogue wave. The survivors are left to fight for their lives as they attempt to escape the sinking ship.
定义一个用于全文搜索的工具。
此工具使用全文搜索检索器来检索与指定电影标题相匹配的电影文档。然后,该工具返回指定电影的剧情。
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."
准备 LLM。
以下代码通过执行以下操作为代理准备 LLM:
指定使用哪个 LLM。默认情况下,
ChatOpenAI
类使用gpt-3.5-turbo
。定义一个 LangChain 提示模板,以指示 LLM 如何生成响应,包括如何处理工具调用。
将工具和提示模板绑定到 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
测试工具调用功能。
您可以运行以下代码片段,以测试 LLM 是否根据查询正确调用工具,方法是检查 LLM 正在调用的工具的名称:
# 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'}]
构建图表
在本节中,您将构建一个图表,用于编排代理工作流程。图表定义了代理响应查询时所采取步骤的顺序。
此代理使用以下工作流程:
代理接收用户查询。
在代理节点中,工具绑定的 LLM 会根据查询生成响应。
此响应包含有关代理是否应使用工具的信息。如果代理确定需要一个工具,它就会将工具配置添加到图表状态,并进入工具节点。否则,代理将不使用工具直接生成答案。
如果响应显示需要工具,工作流程将继续到工具节点。
在此节点中,代理从图表状态中读取工具配置。
回到代理节点,LLM 接收检索到的上下文并生成最终响应。
在笔记本中粘贴并运行以下代码,以构建和运行图表:
定义图表状态。
图表状态在整个工作流程中保持图表的状态。它可以包含需要在不同节点之间跟踪和修改的任何共享数据。在本示例中,GraphState
组件使用一个字典来追踪代理的消息,其中包括用户查询、LLM 的响应以及工具调用结果。然而,您可以自定义图表状态,以包含与应用程序相关的任何数据。
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)
定义节点。
对于此代理,您将定义两个自定义节点:
添加代理节点。
该节点处理当前状态中的消息,使用这些消息调用 LLM,并通过 LLM 的响应(包括任何工具调用)更新状态。
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) 添加工具节点。
该节点处理工具调用,根据当前状态确定适当的工具,并将工具调用结果更新到消息历史中。
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)
定义边缘。
边缘连接图表中的节点,并定义代理的流程。在此代码中,您将定义以下边缘:
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}, )
定义执行函数。
定义执行函数,以运行代理的工作流程。以下执行函数在图表中的输出通过节点时对其进行流式传输,您可以实时查看代理的输出:
# 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)
测试该代理。
运行以下示例查询以测试代理。您生成的响应可能会有所不同。
execute_graph("What are some movies that take place in the ocean?")
Node agent: {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ubFQjnq0s4GKoRAuumpaLxSV', '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': 241, 'total_tokens': 263, '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-d2cd95a6-27b1-4f1e-a173-51535209c99d-0', tool_calls=[{'name': 'vector_search', 'args': {'user_query': 'movies that take place in the ocean'}, 'id': 'call_ubFQjnq0s4GKoRAuumpaLxSV', 'type': 'tool_call'}], usage_metadata={'input_tokens': 241, 'output_tokens': 22, 'total_tokens': 263, '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\nLost River: A single mother is swept into a dark underworld, while her teenage son discovers a road that leads him to a secret underwater town.\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\nDagon: A boating accident runs a young man and woman ashore in a decrepit Spanish fishing town which they discover is in the grips of an ancient sea god and its monstrous half human offspring.', id='11001e4d-fef1-4abe-8700-d720876b5dce', tool_call_id='call_ubFQjnq0s4GKoRAuumpaLxSV')]} Node agent: {'messages': [AIMessage(content='Some movies that take place in the ocean are:\n1. 20,000 Leagues Under the Sea\n2. Deep Rising\n3. Lost River\n4. Waterworld\n5. Dagon', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 43, 'prompt_tokens': 495, 'total_tokens': 538, '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-7832ca28-bac1-4e80-a7b9-76cc85034ce7-0', usage_metadata={'input_tokens': 495, 'output_tokens': 43, 'total_tokens': 538, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- Some movies that take place in the ocean are: 1. 20,000 Leagues Under the Sea 2. Deep Rising 3. Lost River 4. Waterworld 5. Dagon
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.
添加内存
要提高代理的性能,您可以持久化其状态。持久性使代理能够存储先前交互的信息,代理可以在未来的交互中使用这些信息,提供更具上下文相关性的响应。
初始化检查点。
要持久化图表执行的状态,您可以使用检查点将状态保存到特定的 thread
,即使在图表执行结束后也可以访问该状态。
MongoDBSaver
检查点允许您使用 MongoDB 作为持久化检查点状态的后端数据库。在笔记本中运行以下代码,初始化检查点并在图表中使用它:
from langgraph.checkpoint.mongodb import MongoDBSaver # Initialize a MongoDB checkpointer checkpointer = MongoDBSaver(client) # Instantiate the graph with the checkpointer app = graph.compile(checkpointer=checkpointer)
更新执行函数。
您还必须更新执行函数以引用 thread_id
,这是您要保留的线程的唯一标识符。在您的笔记本中运行以下代码以更新执行函数:
# 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)
测试该代理。
在笔记本中运行以下代码片段,以测试代理。您生成的响应可能会有所不同。
第一个代码片段运行查询,并将响应保存到指定
thread_id
为1
的线程中。第二个代码片段运行关于上一个交互的查询,从指定
thread_id
为1
的线程中加载状态。它会生成一个能够识别先前交互的响应。
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='', additional_kwargs={'tool_calls': [{'id': 'call_7hzNqOU0hZBHrm7wihISMrEz', 'function': {'arguments': '{"user_query": "Movies similar to Titanic"}', 'name': 'vector_search'}, 'type': 'function'}, {'id': 'call_OHAkJsyjPGKcCpqye2M56Moy', 'function': {'arguments': '{"user_query": "Titanic"}', 'name': 'vector_search'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 1394, 'total_tokens': 1444, '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-e48b75c7-4493-4dcd-af2e-afb556882052-0', tool_calls=[{'name': 'vector_search', 'args': {'user_query': 'Movies similar to Titanic'}, 'id': 'call_7hzNqOU0hZBHrm7wihISMrEz', 'type': 'tool_call'}, {'name': 'vector_search', 'args': {'user_query': 'Titanic'}, 'id': 'call_OHAkJsyjPGKcCpqye2M56Moy', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1394, 'output_tokens': 50, 'total_tokens': 1444, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} Node tools: {'messages': [ToolMessage(content="The Poseidon Adventure: A group of passengers struggle to survive and escape when their ocean liner completely capsizes at sea.\n\nPoseidon: On New Year's Eve, the luxury ocean liner Poseidon capsizes after being swamped by a rogue wave. The survivors are left to fight for their lives as they attempt to escape the sinking ship.\n\nLife of Pi: A young man who survives a disaster at sea is hurtled into an epic journey of adventure and discovery. While cast away, he forms an unexpected connection with another survivor: a fearsome Bengal tiger.\n\nTraffickers: A thriller about the passengers with different objectives on board a cruiser headed for China, being chased over and over again and unexpected happening of things.\n\nAfter the Storm: When a luxury yacht goes down in a violent storm the race is on to salvage the bounty at any cost, causing two couples to commit the ultimate betrayal.", id='f1b40d2d-eaf9-4dca-8f4d-0f69eb4b4f3d', tool_call_id='call_7hzNqOU0hZBHrm7wihISMrEz'), ToolMessage(content="Titanic: The story of the 1912 sinking of the largest luxury liner ever built, the tragedy that befell over two thousand of the rich and famous as well as of the poor and unknown passengers aboard the doomed ship.\n\nThe Poseidon Adventure: A group of passengers struggle to survive and escape when their ocean liner completely capsizes at sea.\n\nRaise the Titanic: To obtain a supply of a rare mineral, a ship raising operation is conducted for the only known source, the Titanic.\n\nPoseidon: On New Year's Eve, the luxury ocean liner Poseidon capsizes after being swamped by a rogue wave. The survivors are left to fight for their lives as they attempt to escape the sinking ship.\n\nAll Is Lost: After a collision with a shipping container at sea, a resourceful sailor finds himself, despite all efforts to the contrary, staring his mortality in the face.", id='3551a58d-44d7-4055-a997-97c09dc563ef', tool_call_id='call_OHAkJsyjPGKcCpqye2M56Moy')]} Node agent: {'messages': [AIMessage(content='Movies similar to "Titanic" include:\n1. The Poseidon Adventure\n2. Poseidon\n3. Life of Pi\n4. Traffickers\n5. After the Storm\n\nThese movies feature themes of survival, disasters at sea, and unexpected events similar to those in "Titanic."', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 62, 'prompt_tokens': 1827, 'total_tokens': 1889, '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-8332baba-75d3-4d18-baf6-75b3cf68b552-0', usage_metadata={'input_tokens': 1827, 'output_tokens': 62, 'total_tokens': 1889, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]} ---FINAL ANSWER--- Movies similar to "Titanic" include: 1. The Poseidon Adventure 2. Poseidon 3. Life of Pi 4. Traffickers 5. After the Storm These movies feature themes of survival, disasters at sea, and unexpected events similar to those in "Titanic."