mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-20 06:35:46 +02:00
596 lines
32 KiB
Plaintext
596 lines
32 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
|
||
"metadata": {},
|
||
"source": [
|
||
"# How to add persistence (\"memory\") to your graph\n",
|
||
"\n",
|
||
"Many AI applications need memory to share context across multiple interactions. In LangGraph, memory is provided for any [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) through [Checkpointers](https://github.com/langchain-ai/langgraph/tree/e4ca7ab69c599fd77dd4f0d47280849d715392cc/libs/checkpoint).\n",
|
||
"\n",
|
||
"When creating any LangGraph workflow, you can set them up to persist their state by doing using the following:\n",
|
||
"\n",
|
||
"1. A [Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#basecheckpointsaver).\n",
|
||
"2. Call `compile(checkpointer=my_checkpointer)` when compiling the graph.\n",
|
||
"\n",
|
||
"There are several options for checkpointers to use.\n",
|
||
"\n",
|
||
"1. [MemorySaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#memorysaver) is an in-memory key-value store for Graph state.\n",
|
||
"2. [SqliteSaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#sqlitesaver) allows you to save to a Sqlite db locally or in memory.\n",
|
||
"3. There are various external databases that can be used for persistence, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/), [MongoDB](https://langchain-ai.github.io/langgraph/how-tos/persistence_mongodb/), and [Redis](https://langchain-ai.github.io/langgraph/how-tos/persistence_redis/).\n",
|
||
" \n",
|
||
"Here is an example using [MemorySaver](https://langchain-ai.github.io/langgraph/reference/checkpoints/#memorysaver) in memory:\n",
|
||
"```python\n",
|
||
"from langgraph.graph import StateGraph\n",
|
||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||
"\n",
|
||
"builder = StateGraph(....)\n",
|
||
"# ... define the graph\n",
|
||
"memory = MemorySaver()\n",
|
||
"graph = builder.compile(checkpointer=memory)\n",
|
||
"...\n",
|
||
"```\n",
|
||
"\n",
|
||
"This works for [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph) and all its subclasses, such as [MessageGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#messagegraph).\n",
|
||
"\n",
|
||
"Below is an example.\n",
|
||
"\n",
|
||
"<div class=\"admonition tip\">\n",
|
||
" <p class=\"admonition-title\">Note</p>\n",
|
||
" <p>\n",
|
||
" In this how-to, we will create our agent from scratch to be transparent (but verbose). You can accomplish similar functionality using the <code>create_react_agent(model, tools=tool, checkpointer=checkpointer)</code> (<a href=\"https://langchain-ai.github.io/langgraph/reference/prebuilt/#create_react_agent\">API doc</a>) constructor. This may be more appropriate if you are used to LangChain’s <a href=\"https://python.langchain.com/v0.2/docs/how_to/agent_executor/#concepts\">AgentExecutor</a> class.\n",
|
||
" </p>\n",
|
||
"</div> "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "7cbd446a-808f-4394-be92-d45ab818953c",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Setup\n",
|
||
"\n",
|
||
"First we need to install the packages required"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "af4ce0ba-7596-4e5f-8bf8-0b0bd6e62833",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"%%capture --no-stderr\n",
|
||
"%pip install --quiet -U langgraph langchain_anthropic"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "0abe11f4-62ed-4dc4-8875-3db21e260d1d",
|
||
"metadata": {},
|
||
"source": [
|
||
"Next, we need to set API keys for OpenAI (the LLM we will use) and Tavily (the search tool we will use)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 21,
|
||
"id": "c903a1cf-2977-4e2d-ad7d-8b3946821d89",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import getpass\n",
|
||
"import os\n",
|
||
"\n",
|
||
"\n",
|
||
"def _set_env(var: str):\n",
|
||
" if not os.environ.get(var):\n",
|
||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||
"\n",
|
||
"\n",
|
||
"_set_env(\"ANTHROPIC_API_KEY\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f0ed46a8-effe-4596-b0e1-a6a29ee16f5c",
|
||
"metadata": {},
|
||
"source": [
|
||
"Optionally, we can set API key for [LangSmith tracing](https://smith.langchain.com/), which will give us best-in-class observability."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 22,
|
||
"id": "95e25aec-7c9f-4a63-b143-225d0e9a79c3",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||
"_set_env(\"LANGCHAIN_API_KEY\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "4cf509bc",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Set up the State\n",
|
||
"\n",
|
||
"The state is the interface for all the nodes."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 37,
|
||
"id": "14619607",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from typing import Annotated\n",
|
||
"\n",
|
||
"from typing_extensions import TypedDict\n",
|
||
"\n",
|
||
"from langgraph.graph.message import add_messages\n",
|
||
"\n",
|
||
"# Add messages essentially does this with more\n",
|
||
"# robust handling\n",
|
||
"# def add_messages(left: list, right: list):\n",
|
||
"# return left + right\n",
|
||
"\n",
|
||
"\n",
|
||
"class State(TypedDict):\n",
|
||
" messages: Annotated[list, add_messages]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "21ac643b-cb06-4724-a80c-2862ba4773f1",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Set up the tools\n",
|
||
"\n",
|
||
"We will first define the tools we want to use.\n",
|
||
"For this simple example, we will use create a placeholder search engine.\n",
|
||
"However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/v0.2/docs/how_to/custom_tools) on how to do that.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 24,
|
||
"id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_core.tools import tool\n",
|
||
"\n",
|
||
"\n",
|
||
"@tool\n",
|
||
"def search(query: str):\n",
|
||
" \"\"\"Call to surf the web.\"\"\"\n",
|
||
" # This is a placeholder for the actual implementation\n",
|
||
" return [\"The answer to your question lies within.\"]\n",
|
||
"\n",
|
||
"\n",
|
||
"tools = [search]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "01885785-b71a-44d1-b1d6-7b5b14d53b58",
|
||
"metadata": {},
|
||
"source": [
|
||
"Now we can create our [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tool+node#toolnode). This \n",
|
||
"object actually **runs** the tools (aka functions) that the LLM has asked to use."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 25,
|
||
"id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langgraph.prebuilt import ToolNode\n",
|
||
"\n",
|
||
"tool_node = ToolNode(tools)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "5497ed70-fce3-47f1-9cad-46f912bad6a5",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Set up the model\n",
|
||
"\n",
|
||
"Now we need to load the [chat model](https://python.langchain.com/v0.2/docs/concepts/#chat-models) to power our agent.\n",
|
||
"For the design below, it must satisfy two criteria:\n",
|
||
"\n",
|
||
"1. It should work with **messages** (since our state contains a list of chat messages)\n",
|
||
"2. It should work with [**tool calling**](https://python.langchain.com/v0.2/docs/concepts/#functiontool-calling).\n",
|
||
"\n",
|
||
"<div class=\"admonition tip\">\n",
|
||
" <p class=\"admonition-title\">Note</p>\n",
|
||
" <p>\n",
|
||
" These model requirements are not general requirements for using LangGraph - they are just requirements for this one example.\n",
|
||
" </p>\n",
|
||
"</div> \n",
|
||
" "
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 26,
|
||
"id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langchain_openai import ChatOpenAI\n",
|
||
"\n",
|
||
"# We will set streaming=True so that we can stream tokens\n",
|
||
"# See the streaming section for more information on this.\n",
|
||
"model = ChatOpenAI(temperature=0, streaming=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a77995c0-bae2-4cee-a036-8688a90f05b9",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n",
|
||
"After we've done this, we should make sure the model knows that it has these tools available to call.\n",
|
||
"We can do this by converting the LangChain tools into the format for OpenAI function calling, and then bind them to the model class.\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 27,
|
||
"id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"bound_model = model.bind_tools(tools)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e03c5094-9297-4d19-a04e-3eedc75cefb4",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Define the graph \n",
|
||
"\n",
|
||
"We now need to define a few different nodes in our graph.\n",
|
||
"In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/v0.2/docs/concepts/#langchain-expression-language-lcel).\n",
|
||
"There are two main nodes we need for this:\n",
|
||
"\n",
|
||
"1. The agent: responsible for deciding what (if any) actions to take.\n",
|
||
"2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action.\n",
|
||
"\n",
|
||
"We will also need to define some edges.\n",
|
||
"Some of these edges may be conditional.\n",
|
||
"The reason they are conditional is that based on the output of a node, one of several paths may be taken.\n",
|
||
"The path that is taken is not known until that node is run (the LLM decides).\n",
|
||
"\n",
|
||
"1. Conditional Edge: after the agent is called, we should either:\n",
|
||
" a. If the agent said to take an action, then the function to invoke tools should be called\n",
|
||
" b. If the agent said that it was finished, then it should finish\n",
|
||
"2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n",
|
||
"\n",
|
||
"Let's define the nodes, as well as a function to decide how what conditional edge to take."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 28,
|
||
"id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Define the function that determines whether to continue or not\n",
|
||
"from typing import Literal\n",
|
||
"\n",
|
||
"\n",
|
||
"def should_continue(state: State) -> Literal[\"action\", \"__end__\"]:\n",
|
||
" \"\"\"Return the next node to execute.\"\"\"\n",
|
||
" last_message = state[\"messages\"][-1]\n",
|
||
" # If there is no function call, then we finish\n",
|
||
" if not last_message.tool_calls:\n",
|
||
" return \"__end__\"\n",
|
||
" # Otherwise if there is, we continue\n",
|
||
" return \"action\"\n",
|
||
"\n",
|
||
"\n",
|
||
"# Define the function that calls the model\n",
|
||
"def call_model(state: State):\n",
|
||
" response = model.invoke(state[\"messages\"])\n",
|
||
" # We return a list, because this will get added to the existing list\n",
|
||
" return {\"messages\": response}"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b",
|
||
"metadata": {},
|
||
"source": [
|
||
"We can now put it all together and define the graph!"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 29,
|
||
"id": "812b4e70-4956-4415-8880-db48b3dcbad2",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langgraph.graph import StateGraph, START\n",
|
||
"\n",
|
||
"# Define a new graph\n",
|
||
"workflow = StateGraph(State)\n",
|
||
"\n",
|
||
"# Define the two nodes we will cycle between\n",
|
||
"workflow.add_node(\"agent\", call_model)\n",
|
||
"workflow.add_node(\"action\", tool_node)\n",
|
||
"\n",
|
||
"# Set the entrypoint as `agent`\n",
|
||
"# This means that this node is the first one called\n",
|
||
"workflow.add_edge(START, \"agent\")\n",
|
||
"\n",
|
||
"# We now add a conditional edge\n",
|
||
"workflow.add_conditional_edges(\n",
|
||
" # First, we define the start node. We use `agent`.\n",
|
||
" # This means these are the edges taken after the `agent` node is called.\n",
|
||
" \"agent\",\n",
|
||
" # Next, we pass in the function that will determine which node is called next.\n",
|
||
" should_continue,\n",
|
||
")\n",
|
||
"\n",
|
||
"# We now add a normal edge from `tools` to `agent`.\n",
|
||
"# This means that after `tools` is called, `agent` node is called next.\n",
|
||
"workflow.add_edge(\"action\", \"agent\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "bc9c8536-f90b-44fa-958d-5df016c66d8f",
|
||
"metadata": {},
|
||
"source": [
|
||
"**Persistence**\n",
|
||
"\n",
|
||
"To add in persistence, we pass in a checkpoint when compiling the graph"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 30,
|
||
"id": "6845ed6a-d155-4105-9160-28849877248b",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||
"\n",
|
||
"memory = MemorySaver()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 31,
|
||
"id": "79d29875-8aa8-434c-9f20-1c58346a6249",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Finally, we compile it!\n",
|
||
"# This compiles it into a LangChain Runnable,\n",
|
||
"# meaning you can use it as you would any other runnable\n",
|
||
"app = workflow.compile(checkpointer=memory)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "7654ebcc-2179-41b4-92d1-6666f6f8634f",
|
||
"metadata": {},
|
||
"source": [
|
||
"<div class=\"admonition tip\">\n",
|
||
" <p class=\"admonition-title\">Note</p>\n",
|
||
" <p>\n",
|
||
" If you're using LangGraph Cloud, you <strong>don't need</strong> to pass checkpointer when compiling the graph, since it's done automatically.\n",
|
||
" </p>\n",
|
||
"</div>"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 32,
|
||
"id": "0d49697f",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADaANEDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwMECAkCAf/EAE8QAAEEAQIDAwYICAsGBwAAAAEAAgMEBQYRBxIhEzFVFBYiQZTRCBUXUWF1k+EjMjU3QnGBswkzOGJykZKhsbLSRlJUVoKVGCQlRVPB8P/EABsBAQACAwEBAAAAAAAAAAAAAAACAwEEBQYH/8QANhEAAgECAQgHBgcBAAAAAAAAAAECAxETBBIhMUFRUpEFFBVhcaGxIjNigcHwMjRCY9Hh8bL/2gAMAwEAAhEDEQA/APqmiIgCIiAIiIDpXc1j8bII7d6tVkI5gyaZrCR8+xP0Lg86sL4xQ9pZ71n+rsfVv8TbnlNaGxy4epy9rGHbfhrXduuPzexfhtP7BnuWnlOW0clqYUotuyezak/qdSlkWLBTztZonnVhfGKHtLPennVhfGKHtLPes783sX4bT+wZ7k83sX4bT+wZ7lq9q5PwS5ot7O+LyNE86sL4xQ9pZ7086sL4xQ9pZ71nfm9i/Daf2DPcnm9i/Daf2DPcnauT8EuaHZ3xeRonnVhfGKHtLPennVhfGKHtLPes783sX4bT+wZ7k83sX4bT+wZ7k7VyfglzQ7O+LyNE86sL4xQ9pZ7086sL4xQ9pZ71nfm9i/Daf2DPcnm9i/Daf2DPcnauT8EuaHZ3xeRpFfUWKtTMhgydOaV52bHHYY5zj9ABUgsbs4mjTzem5K9KvBJ8awjnjia07bO9YC2RdOlVhXpKrBNJ319xz8oo4Es29wiIpmsEREAREQBERAEREAREQBERAEREBmeo/wA51/6np/vrS5Fx6j/Odf8Aqen++tLkXl+lvzb8I/8AKPT5J7mIVd1txBwHDvH17mfv+RRWZhXgZHDJPLNIQTysjja57jsCegOwCsSzDjzjsdcwuGnt0NTS3ad7tqGS0nVdYt46bs3jtSxoO7CCWFpa4Hm6j1jl00pSSZszbUW0dTM/CN0/jNb6UwsUN25Qz2PmyDMhXx9uUtDXMbG0MZCSeYudzE7cnKOYDnCsOc426K01qoadyea8jynaxQObJVm7FkkgBja+cM7NpcHN2DnDvCyepltaY7KcKNcav01lr9yHFZOhlI8RjzNYhklfCYHyQR7lvOyHdwHRrjsdgqxx0xmrtYVeIuNu4vWmTyXbxnT1DERysxJpMbFJzvLSGSS8wl3ZIXO5g0Mb3LcVGDkl9e+xqurNJv6dx6EvcY9JY/V8+lpMlNLn4JYYpqNajYnfGZQ0xucWRkBhD27vJ5RvsSCong3xvx3F+HKeTU7lGxSuWYOznpWGMdFHMY2P7SSJjedwAJjB5mbkEdCurw1xVqPjFxTzEuOtVamTOJdVs2az4hO1tPZwaXAb8riQR+idwdiulwHnvabu6o0llMHl6VtmcyeRivy0n+Q2IJrJkjMc/wCIXFsg9HfccrtwNlS4QUXZadG3u0lilJyV9Wn+jYURFqmyRuS/K+m/rWH/AActaWS5L8r6b+tYf8HLWl7Lo/8AKQ8Wefy/3q8AiIt45gREQBERAEREAREQBERAEREAREQGZ6j/ADnX/qen++tKuao4YaQ1teju6g0xic3bijELJ79OOZ7WAkhoLgSBu4nb6StI1Bw/oahy/wAZy279S0YGVnGnY7MOY1z3N3Gx7i939a6HyVUfGM37b9y5+VZD1mtjRqZuhLU9iS+h2KOV04U1CSuZeeAXDQsDDoLThYCSG/FkOwJ23P4v0D+pT+lNAaZ0KLQ05gMbgha5TOMfVZD2vLvy83KBvtzO23+cq4/JVR8Yzftv3J8lVHxjN+2/ctR9Fzas63qWrLaC0qJGopL5KqPjGb9t+5ZFrqrdwHwjeF+jKmbygwmoKeUnvMfY3kc6CJro+V23o9Sd/nUOx/3VyZPtCluZpajNQ6axOrcXJjc3jauWx8ha59W7C2WNxB3BLXAjoeqsvyVUfGM37b9yfJVR8Yzftv3LK6Ia0qquTMdfpPQ0zLT8H7hkf9gNN/8Aa4f9KkMDwe0LpfLQZTD6PwmLyMHN2VupQiilj3aWnlcGgjcEj9RK0L5KqPjGb9t+5Pkqo+MZv237lN9Fzeh1vUj12gv0+SK3kvyvpv61h/wctaVOp8LsbVyFO46/lLT6konjjsWuZnON9iRt171cV1aFFZPRjSzr2v5nOyqtGtNSiERFaaYREQBERAEREAREQBERAEREAREQBERAEREAXnfir/LN4F/Vud/cMXohed+Kv8s3gX9W539wxAeiEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEREAXnfir/LN4F/Vud/cMXohed+Kv8s3gX9W539wxAeiEREAREQBERAEREAREQBERAEREAREQBERAEREAREQBERAEUBqPWdLTsrKxZLeyMjQ5lKq0Ok5SSA5xJAY3cH0nEb7EDcjZVeTXWqJzzQ4rF1GnuZPaklcP18rGj+on9qtVJtXdl4svhQqVFeKLnqnTlLWGmcvgckx0mOylOajZY13KXRSsLHgH1dHHqvhPxQ4VZnhfxTzOhbkL7OUoXfJYhEwk2Wu2MT2NG59NrmOA7/SA719ovPPV3/DYT+1Msj1rwYOu+N2lOJ2RqYn45wEfK2u0ydjZe0l0L5Om/NG5xcCO8hu/RuylhLiXMs6pW3GnfBd4Lw8BuC2B0vyNGTLPLMpI3Y9pckAMnUd4bs2MH1tjatYWa+eerv+Gwn9qZf1uttWR7F1DDWP5gsSxb/9XI7/AATC+Jcx1StwmkoqlgeIdbI24qOSqTYbISnljZMQ+GY/MyVvQn+a7lcfUCAraq5QlDWa0oyg7SVgiIoEQiIgCIiAIiIAiIgCIiAIiIAiIgCIiAKD1lqF2msHJZhjZNdke2CrC87NfK47N3+gdXH17NKnFQOJr3OzOlYj/FeUTy9f98QuDf27Of8A3q6kk5adl3yVy2lFTmosh6FEU2SOfI+zamd2li1Lt2k8hGxc4/qAAA6NADQAAAOyi808ZuMGptJ57UWW0xnMplcbp2euy/jYcNWONrk9n2kM1p7hK6Qh/N+C35OdoI7ytaUnN3es9JKSpR1aD0si8u8X+LOrqt3iR8Vaxi01d05NVp4vTrKkEljJ9tHGe0Bka55c90jms5BsCz0g7qpLUnEniJqnW2raGk4c5DT07Ybj4RjMfjrEdix2LJHGy6zOx4bu8ACIN6DfmJOwjYg6yvaz+/8AD0gvy6RrHNa5wa5x2aCepO2/RYNSz/EbW+uLeDfnjomzX0tjspYpV6Ve06G9K6dsjA94cDHuwAjqTyt5XN9LmqcOVz3FrVfATUTdRWtPXstgsjLL5BWrvbHK2OEyuYJY3/xm4BB32DRy7HckHVWxfd7HqK1Viu13wTsEkTxs5p//AHQ/SrJw/wA9PcZcxF6V01zH8hZM92754HA8j3fzt2vafnLN/WoBNMvdHxLotZ3TYm32g+flmrcp/Zzu/tFbNH2k6b3N/NafRWKMsgpU3LajTkRFWcAIiIAiIgCIiAIiIAiIgCIiAIiIAiIgCqvEXDT5LCw26cT57uNnFuKGP8aUBrmvYPnJY52w9bg39atSKcJZklIlGTjJSWwy2raiu1orEEjZoJWh7JGHcOaRuCFluq/g5af1bLqOOfMZ+lis/KbN/EUbrYqsljka3t9uQuDvQYdubkJaN2lX+LNYbVHEHVOD0XcMuXwj435ihPXlZSE0m5Ijm5eVkp6Oc0bgl2+wcXuXbllzNQ8tnTGUY/1mARTN/YWvP94CzguWmm7r5X+/A70a9KtH2jzrxQ4Z66m4iZHM6VoZ2TLOghjoZ05TGOqseyJreaWKauZo2825cyIlriXEBpcVpGU4GVczmZ84zUOd03mclXiizB05cFeG+9jOUPc1zHFpA6BzC1223VX/AOML/wDy5mvZPvUVc11Ux2ex+Et1LdXNZBr308dMI2WLLWAl5jjL+Z4aASdgdgOqdXq7iSdG7bl5iloOhQ1rkdTsntPyF7H18bKyR7TGI4XSOa4ejzcxMrtySR0HQeup/wDh8wcGltH4ejmM5i5tKMfFjctSsxsttY9vLI15MZY4OG2/oeobbLQfjC//AMuZr2T71/W28pLsIdMZmV57mmGOP+972j+9Or1d3oTc6L1tHciZ2UTGFzn8oA5n95+k/SpPh1QdeymQzzgfJ3MbSpnfcPY080kg+hziG/T2W/cV18ZojK5x7XZsRY3HfpY+CTtJpvokkGwa352t5t/94dQZLRPFDTWrtR6k0th3Tw5TS8zKt6lPSkriJpB7NzOZoa6Nwa7lLehDdx02JkkqSavdvy+f3o8udlWUxmsyBc0RFScsIiIAiIgCIiAIiIAiIgCIiAIiIAiLONacSr2S01qivwt+J9Y61w80VOXGS32sjqyvIG8xB/QaXOLdwTyObuHDZAaBatsqsedjLKI3SNgj27SQN235QSN+8D9ZHzrIoqWc+ETo7TOXsP1Zwojq5fy6XEl0cNu9BE4mFsu25ja4hjiw/M5pDhyvVqx3CrEWdeY/iFl6LTriPFMxz5YbUslauDuZRC12w6lxHMWgkAdB13vSA/DImRue5rGtc88zyBsXHYDc/OdgB+wL9oiA47FiKpBJPPIyGGNpe+SRwa1rQNyST3ABfF3jt8J7Ma5+E0/iRhLT4I8LcjjwYO4Da0LzybjodpCXPc0//I4dy+xus9LVdc6Pzum70s8FLMUJ8fPLVcGysjljdG5zCQQHAOJBII326FfPbWv8H/w8038IHhvoWtmdTPxGpKmSntzS2q5njdXia9gjcIA0Ak9d2n6NkB744W8RMZxZ4e4HV2HdvQy1Vs7WE7mJ/c+NxH6THhzT9LSrUs74GcDsF8H7SFnTWnLuUuYuW9LeYzKTtmdXL2sBjjLWN2jHJuAdzu5xJO60RAFAa40ZT15pXL4G5YuUYMnXNaW3jZzBZY31ckg6gjc/OOpBBBIU+iAzWGTWXD/J6C0xjcPY1lps1zTy2psjk2i7XkY0Fk0jXD8IHcrt9uu7h3belbNK6805rg5EafzdHMOx1l9O42nO2R1eZri1zHgdWncHv7+8bhTyomrOGkh01qOLQdypoPU+XlZafmqePikMk7C0h0rCNpOYN5ST12cfWgL2iz6HiTY0/rnTGhc1istfyuRxvbu1FTx5GLfYja7tWOfuezceQuDTuNnNG+6v0UrJ2B8b2yMPc5p3BQH7REQBERAEREAREQBERAFH6gztHS2ByWayc3k2Nx1aW5amDHP7OKNhe93K0EnZoJ2AJPqUgv45oe0tcA5pGxBG4IQGV4vUmo+MdXh/q/Q+dbp/Rdh8lzJ1Mpij5behB5Y42c52ja7Z55x12LHNJB2N+0/pDB6TN84XEUsU6/ZfbtupwNiNiZxJdI8gek4knqVC8KL+tcho9kmv8bQxmpG2Z2Pixj+aB0Qkd2T2+m8jdnLuC4nffu7lcUAREQBERAF5t4mZOnc+G9wYoQWoZrtLFZmSzXjeHSQNfAORz2jq0O5Xbb9+xXb4mcf87q3VtvhpwYggzOrovQy2ophzY3ANJ2Je7YiSYbHaMb7EHcHlc1XfgjwAwfBajbsRTz5/VmUd2uY1Nkjz3L8h6ndxJLWb9zAdhsN9z1IGoIiIAiIgCIiALHrHCvK8GeH2Vp8FaGOZk7OU+M3Y/UNyeSs4O27aOJ3MTGXcvTrsC4krYUQFbxevsNkdYXdIi9E7VGPpQ3rtGNryIo5CQ1weWgEbtPTv22JA3CsizrDZbteOmo8f5g+Qdjia0vnp2G3xhu4/+V7Tshv2fft2jtt/xQtFQBERAEREAREQBERAFR+M3FilwT0Fc1dk8Plszi6T2C0zDRwyTQMceXtS2SSPdgcWg8pJHMDtyhxF0nsRVYzJNKyJg73SODR/WVD5HM6by1CzRvX8Zbp2Y3Qz15543MkY4EOa5pOxBBIIKkoylqQPlrB/CQa40/rXVea03hqYp6gtRWfi3UNufIspFkTWckDmOh5Wl3M4jbbq0ADYl31F4eZjKah0BpnK5uvDUzV7GVbN6vXaRHFO+JrpGtBJIaHEgbknYd5Xy74z/Axh0b8IbS+N05MzJ6A1LlYmRywzdqcdGXgzRzOBOzWM5nNc7va07klrivqg3U+DY0Nblse1oGwAss2A/rUsOfCzNmSqKL86sL4xQ9pZ71Gap4m6W0ZprJZ/LZ2lWxWOhM1mdsok5Wj5mt3JJJAAA3JIAWHCa0tMWLBdu18bTnt254qtWBjpZZ5nhjI2Abuc5x6AAAkkrzDl+IerfhY5W1pvhlcs6X4ZwSGDL68awsnv7HZ9fHg+rvBl9Xq22Afx09M6t+GVagymra97RnBpkgmo6aLjFfz4B3bLbIO8cJ6ERg7nv3/FevT2IxFHAYyrjcZTgx+Pqxthgq1oxHHEwDYNa0dAAPUFAwV/hlwv01wg0lV05pXGR4zGQekQ3rJM8/jSSPPV7zt1J+gDYAAWtEQBERAERcNu7XoQma1PFWiB2Mkrw1u/6yspN6EDmRRfnVhfGKHtLPennVhfGKHtLPep4c+FmbMlFReOPEm3wg4U6h1lSwT9STYiFlh2NZY7AyR9o1sju05H8oYwuefRPRh7u9WbzqwvjFD2lnvXBfzmncpRsU7eRxtmpYjdDNDLYjcyRjhs5pBPUEEjZMOfCxZnznpfwq+oINdZHKWNHeU6bnqRw1cB8aRs8mmB9ObygVed/MOnKRsPUvolw+1Dk9WaIwmazGG83slkKrLU2KM5ndV5xzBjnljN3AEbjlGx3HXbdfM7hd8EKnR+GXdwGUngl0Fp+f44juSytMNuuTzVoOcnZzi4hrx6xHJ9C+n3nVhfGKHtLPemHPhYsyURRfnVhfGKHtLPennVhfGKHtLPemHPhYsyURRfnVhfGKHtLPepCvYitQsmgkZNE8btkjcHNcPoIUXGUdaFjkREUTAVQ1dq6epbGJxIYcgWh89mQc0dRh7un6Ujv0W9wALndOVr7XYnZVryzSHaONpe4/QBuVkOmnyW8VHkZ9jbyR8tncN+rngEDr6mt5Wj6GhWxtGLqPZq8TdyWiqs/a1I/j9NUbc3b5GM5i2RsbOR2meeu/QEcrR9DQB9C5vN/Fj/ANtp/YM9y6Gs9dYTh9i48jnbb6lWWYQRmKvLYe+QgkNayNrnE7Nceg9RUdFxe0dNoh+rxn6rdOscWPuyczOV4dymMsIDw/m6cm3Nv02VbrVJa5M7qzI6FYsHm/i/Daf2Dfcnm/i/Daf2Dfcsv1f8JjTWD0xRzWMZcysM+Zq4mWM463FJD2j287jGYefcMdzNbsOc7Bu5ICnp+KlS3rnRmFx9uKJmbrz3HV8jjbkNiaJsbizsnOjDGPDmkvZKQ7l22G5G8cSpxMxnw1XLl5v4vw2n9g33Ljn0thrLCyXE0ZGkEbOrsPf+xVjCccND6i1KzA47PR2MjLJJDDtBK2GxJHvzsimLBHI5ux3DHE9D8y4sXx70JmcrTx1POiWxbtOowvNSdsLrLXOaYDKWBjZN2nZhcHHoQCCN84tRapPmZzob0XjGXcjpBwkx0k1/Ht/jMVNLzeiO/sHu6tf8zSeQ93ob840vGZOtmcfBdpyiatO3nY8Ajp8xB6gjuIPUEEHqs7Xe4c2jSz+bxIIFd7Y8hCwb+i55c2UfQC5jXdPW937blJ1oty1rTff4/wA+PcczLKEVHEiaAiIqTkBERAFR+LkMdjBYyOVjZI3ZSsHMeNwRzHvCvCpXFb8jYr61rf5iraeiV13+hVV93LwfoVzzexfhtP7BnuTzexfhtP7BnuUgi8vi1OJ8z5vny3kf5vYvw2n9gz3J5vYvw2n9gz3LvSSMhjdJI4MY0FznOOwAHeSVSNMcbtE6yzkWIxGcZZvTh7q7X15omWg0buMEj2BkwA67xl3Tr3LKqVXpUn5k06kk2r6C0+b2L8Np/YM9yeb2L8Np/YM9yp2mePmg9Y5HF0sRnhalyYPkUhqTxw2HBpc6Nsr2BhkAB3j5uYbHcDZVnjB8JPA6Fx2Vo4XI1b+qqVqrVNSSrPLXY+SeNr43ysAY2QRuc4NLwdwOh7lNSrN2u/MsjTrykoWd/mav5vYvw2n9gz3J5vYvw2n9gz3KQRV4tTifMoz5byP83sX4bT+wZ7lauE7Qzh7h2tAa0MeAB3D8I5Qqm+FP5v8AEf0ZP3jl2MinKVKpnO+mPpI9N0LJt1Lvd9S2oiLbPTnWyVQZDHWqpOwnidHv824I/wDtZLpWRz9N40Pa5kscDYZGOGxa9g5Xg/qc0hbGs61VgZdOZGzlakDpsVbeZbkcQ3fWlIAMob643belt1a70tiHOLLorPg6a161/H3usdDI6qpzaltMk47Ws/BFphmP+PGadlvubnJdMwulyDYeyeYxGGAvDDIGh7mDmA7iNysXwmjdQ47AzZGLS2orNbCcQ/OQ4nJNdLduUX1mtbKxz3HtpWucX8pcXczSDs5etq1mG5AyevKyeGQczJI3BzXD5wR0K5Fq6VoZ15U853uYdxQ1Be4j8PIMnh9KajYcHn8Zkn0r2NfXtWoYbEckhhhds9xDQehAJIO2/RdjWUF7iFrnhhlcdjMvQpuhzMc01ujLBJSL63ZsdK1w3jJcPR5tt+my2lFgy6d9b3eR5b09i89ltHcKOHrNH5jE5fSuXoWcnkLNMx0Io6hJkkisfiymbuAZufwh5ttiu3S0hnI/g/acoHC5BuTg1sy66r5K8TRxDNvk7Ut23DezPPzbbcp332XplEuRVFb9lguzoOubOtsvbAPZ1aUNbmI6F7nve4D9TQw/9QUVNdkmuDHY6IXss8btrNdsIwe58ruvJGPW7bc7bNDnbNOh6W07FpjFCq2Tt53vdNYsFvKZpXfjO23Ow7gBudmho3Oy2oJ04OT2qy/n6f4amW1UoYa1sl0RFUcQIiIAqVxW/I2K+ta3+Yq6qlcVvyNivrWt/mKsp/i5+hVW91LwfoRiKN1DpvE6txcmNzeNq5bHyFrn1bkLZY3EHcEtcCDsQCqkPg/8MxvtoDTfXv8A/S4f9K8qrbT5vFQt7TfL+yW4qabu6x4Z6rwWNmEGQyWLs1K8jncoEj4nNbufUNzsSsc4P6Yw2VyWlYcnpXiFQzuDiE5Oeu3pMbSssj7M9k6SYxPBDnhpjBHKeu3ctZw3BfQOnsnXyOL0ZgsfkK7ueG1Wx8Uckbttt2uDdwequamp5qsi9VcyDhFvT8jzJpbSGcrcD+BNGTCZCK/jNRUp7lZ1SQS1Yx5QHvkbtuxoDhuXbD0h86r2WqZ7B8F9Q8NptGajt6iGf8qOTp4x89S/G/KNsiz2zdwT2ZALT6Q5eo2HT14ili6btbbliyp3u47b/O9wiotvgTw5v25rVnQunrFmd7pJZZMbC5z3E7lxJb1JJJ3XCPg+8Mh/sBpv/tcP+lVWjv8Avma1qe98v7NAU3wp/N/iP6Mn7xyrWLxdPCY6tQx9WGlRrRiKGtXYGRxsA2DWtHQAfMrLwp/N/iP6Mn7xy7GQe6qeMfSR6LoTXU+X1LaiIt49SEREBV8nw3wOTsyWRWlo2ZDu+XH2JK5ed9yXBhAcd/WQSuh8lFDxfNe2/cruivVeov1FiqzjoUmUj5KKHi+a9t+5PkooeL5r237ld0Wcepv9CWNU4mUj5KKHi+a9t+5fuPhRit/w97MWWdxY/ISNB/sFpV0RYx6m8Y1TiZ0MNgcfp6r5NjacVOEnmc2Juxcfnce8n6T1XfRFS25O7ZTrCIiwAiIgCidTaZq6rxzKdt88TGSsnY+vJyPa9p3BBUsilGTi7oFJ+Sqj4xm/bfuT5KqPjGb9t+5XZFLEfdyRTg0uBckUn5KqPjGb9t+5Pkqo+MZv237ldkTEfdyQwaXAuSKT8lVHxjN+2/cnyVUfGM37b9yuyJiPu5IYNLgXJFJ+Sqj4xm/bfuT5KqPjGb9t+5XZExH3ckMGlwLkik/JVR8Yzftv3KzYDB1tN4etjahkNeu0tYZXczjuSTufX1JUgiw5yatsJxhCH4Ul4IIiKBM//9k=",
|
||
"text/plain": [
|
||
"<IPython.core.display.Image object>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data"
|
||
}
|
||
],
|
||
"source": [
|
||
"from IPython.display import Image, display\n",
|
||
"\n",
|
||
"try:\n",
|
||
" display(Image(app.get_graph().draw_mermaid_png()))\n",
|
||
"except Exception:\n",
|
||
" # This requires some extra dependencies and is optional\n",
|
||
" pass"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Interacting with the Agent\n",
|
||
"\n",
|
||
"We can now interact with the agent and see that it remembers previous messages!\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 33,
|
||
"id": "cfd140f0-a5a6-4697-8115-322242f197b5",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||
"\n",
|
||
"hi! I'm bob\n",
|
||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||
"\n",
|
||
"Hello Bob! How can I assist you today?\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"from langchain_core.messages import HumanMessage\n",
|
||
"\n",
|
||
"config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
|
||
"input_message = HumanMessage(content=\"hi! I'm bob\")\n",
|
||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||
" event[\"messages\"][-1].pretty_print()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 34,
|
||
"id": "08ae8246-11d5-40e1-8567-361e5bef8917",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||
"\n",
|
||
"what is my name?\n",
|
||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||
"\n",
|
||
"Your name is Bob.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"input_message = HumanMessage(content=\"what is my name?\")\n",
|
||
"for event in app.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
|
||
" event[\"messages\"][-1].pretty_print()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "3f47bbfc-d9ef-4288-ba4a-ebbc0136fa9d",
|
||
"metadata": {},
|
||
"source": [
|
||
"If we want to start a new conversation, we can pass in a different thread id. Poof! All the memories are gone!"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 35,
|
||
"id": "273d56a8-f40f-4a51-a27f-7c6bb2bda0ba",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||
"\n",
|
||
"what is my name?\n",
|
||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||
"\n",
|
||
"I'm sorry, I do not know your name as I am an AI assistant and do not have access to personal information.\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"input_message = HumanMessage(content=\"what is my name?\")\n",
|
||
"for event in app.stream(\n",
|
||
" {\"messages\": [input_message]},\n",
|
||
" {\"configurable\": {\"thread_id\": \"3\"}},\n",
|
||
" stream_mode=\"values\",\n",
|
||
"):\n",
|
||
" event[\"messages\"][-1].pretty_print()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e833f994",
|
||
"metadata": {},
|
||
"source": [
|
||
"All the checkpoints are persisted to the checkpointer, so you can always resume previous threads."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 36,
|
||
"id": "8578a66d-6489-4e03-8c23-fd0530278455",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"================================\u001b[1m Human Message \u001b[0m=================================\n",
|
||
"\n",
|
||
"You forgot??\n",
|
||
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
|
||
"\n",
|
||
"I apologize for the confusion. I am an AI assistant and I do not have the ability to remember information from previous interactions. How can I assist you today, Bob?\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"input_message = HumanMessage(content=\"You forgot??\")\n",
|
||
"for event in app.stream(\n",
|
||
" {\"messages\": [input_message]},\n",
|
||
" {\"configurable\": {\"thread_id\": \"2\"}},\n",
|
||
" stream_mode=\"values\",\n",
|
||
"):\n",
|
||
" event[\"messages\"][-1].pretty_print()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "eb20430f",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3 (ipykernel)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.11.9"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|