{
 "cells": [
  {
   "cell_type": "raw",
   "id": "9ede5870",
   "metadata": {},
   "source": [
    "---\n",
    "sidebar_position: 7\n",
    "keywords: [ConfigurableField, configurable_fields, ConfigurableAlternatives, configurable_alternatives, LCEL]\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39eaf61b",
   "metadata": {},
   "source": [
    "# How to configure runtime chain internals\n",
    "\n",
    ":::info Prerequisites\n",
    "\n",
    "This guide assumes familiarity with the following concepts:\n",
    "- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
    "- [Chaining runnables](/docs/how_to/sequence/)\n",
    "- [Binding runtime arguments](/docs/how_to/binding/)\n",
    "\n",
    ":::\n",
    "\n",
    "Sometimes you may want to experiment with, or even expose to the end user, multiple different ways of doing things within your chains.\n",
    "This can include tweaking parameters such as temperature or even swapping out one model for another.\n",
    "In order to make this experience as easy as possible, we have defined two methods.\n",
    "\n",
    "- A `configurable_fields` method. This lets you configure particular fields of a runnable.\n",
    "  - This is related to the [`.bind`](/docs/how_to/binding) method on runnables, but allows you to specify parameters for a given step in a chain at runtime rather than specifying them beforehand.\n",
    "- A `configurable_alternatives` method. With this method, you can list out alternatives for any particular runnable that can be set during runtime, and swap them for those specified alternatives."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2347a11",
   "metadata": {},
   "source": [
    "## Configurable Fields\n",
    "\n",
    "Let's walk through an example that configures chat model fields like temperature at runtime:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "40ed76a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33mWARNING: You are using pip version 22.0.4; however, version 24.0 is available.\n",
      "You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
      "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install --upgrade --quiet langchain langchain-openai\n",
    "\n",
    "import os\n",
    "from getpass import getpass\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = getpass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7ba735f4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='17', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 11, 'total_tokens': 12}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ba26a0da-0a69-4533-ab7f-21178a73d303-0')"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.runnables import ConfigurableField\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "model = ChatOpenAI(temperature=0).configurable_fields(\n",
    "    temperature=ConfigurableField(\n",
    "        id=\"llm_temperature\",\n",
    "        name=\"LLM Temperature\",\n",
    "        description=\"The temperature of the LLM\",\n",
    "    )\n",
    ")\n",
    "\n",
    "model.invoke(\"pick a random number\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0f74589",
   "metadata": {},
   "source": [
    "Above, we defined `temperature` as a [`ConfigurableField`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.utils.ConfigurableField.html#langchain_core.runnables.utils.ConfigurableField) that we can set at runtime. To do so, we use the [`with_config`](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.with_config) method like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "4f83245c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='12', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 11, 'total_tokens': 12}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ba8422ad-be77-4cb1-ac45-ad0aae74e3d9-0')"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.with_config(configurable={\"llm_temperature\": 0.9}).invoke(\"pick a random number\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9da1fcd2",
   "metadata": {},
   "source": [
    "Note that the passed `llm_temperature` entry in the dict has the same key as the `id` of the `ConfigurableField`.\n",
    "\n",
    "We can also do this to affect just one step that's part of a chain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e75ae678",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='27', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 14, 'total_tokens': 15}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-ecd4cadd-1b72-4f92-b9a0-15e08091f537-0')"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt = PromptTemplate.from_template(\"Pick a random number above {x}\")\n",
    "chain = prompt | model\n",
    "\n",
    "chain.invoke({\"x\": 0})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c09fac15",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='35', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 14, 'total_tokens': 15}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-a916602b-3460-46d3-a4a8-7c926ec747c0-0')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.with_config(configurable={\"llm_temperature\": 0.9}).invoke({\"x\": 0})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb9637d0",
   "metadata": {},
   "source": [
    "### With HubRunnables\n",
    "\n",
    "This is useful to allow for switching of prompts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9a9ea077",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptValue(messages=[HumanMessage(content=\"You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\\nQuestion: foo \\nContext: bar \\nAnswer:\")])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.runnables.hub import HubRunnable\n",
    "\n",
    "prompt = HubRunnable(\"rlm/rag-prompt\").configurable_fields(\n",
    "    owner_repo_commit=ConfigurableField(\n",
    "        id=\"hub_commit\",\n",
    "        name=\"Hub Commit\",\n",
    "        description=\"The Hub commit to pull from\",\n",
    "    )\n",
    ")\n",
    "\n",
    "prompt.invoke({\"question\": \"foo\", \"context\": \"bar\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f33f3cf2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatPromptValue(messages=[HumanMessage(content=\"

 You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.
 \\nQuestion: foo \\nContext: bar \\nAnswer: 
\")])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt.with_config(configurable={\"hub_commit\": \"rlm/rag-prompt-llama\"}).invoke(\n",
    "    {\"question\": \"foo\", \"context\": \"bar\"}\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79d51519",
   "metadata": {},
   "source": [
    "## Configurable Alternatives\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac733d35",
   "metadata": {},
   "source": [
    "The `configurable_alternatives()` method allows us to swap out steps in a chain with an alternative. Below, we swap out one chat model for another:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3db59f45",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33mWARNING: You are using pip version 22.0.4; however, version 24.0 is available.\n",
      "You should consider upgrading via the '/Users/jacoblee/.pyenv/versions/3.10.5/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n",
      "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install --upgrade --quiet langchain-anthropic\n",
    "\n",
    "import os\n",
    "from getpass import getpass\n",
    "\n",
    "os.environ[\"ANTHROPIC_API_KEY\"] = getpass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "71248a9f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\\n\\nHow's that? I tried to come up with a simple, silly pun-based joke about bears. Puns and wordplay are a common way to create humorous bear jokes. Let me know if you'd like to hear another one!\", response_metadata={'id': 'msg_018edUHh5fUbWdiimhrC3dZD', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 80}}, id='run-775bc58c-28d7-4e6b-a268-48fa6661f02f-0')"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_anthropic import ChatAnthropic\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.runnables import ConfigurableField\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "llm = ChatAnthropic(\n",
    "    model=\"claude-3-haiku-20240307\", temperature=0\n",
    ").configurable_alternatives(\n",
    "    # This gives this field an id\n",
    "    # When configuring the end runnable, we can then use this id to configure this field\n",
    "    ConfigurableField(id=\"llm\"),\n",
    "    # This sets a default_key.\n",
    "    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
    "    default_key=\"anthropic\",\n",
    "    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`\n",
    "    openai=ChatOpenAI(),\n",
    "    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model=\"gpt-4\")`\n",
    "    gpt4=ChatOpenAI(model=\"gpt-4\"),\n",
    "    # You can add more configuration options here\n",
    ")\n",
    "prompt = PromptTemplate.from_template(\"Tell me a joke about {topic}\")\n",
    "chain = prompt | llm\n",
    "\n",
    "# By default it will call Anthropic\n",
    "chain.invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "48b45337",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Why don't bears like fast food?\\n\\nBecause they can't catch it!\", response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 13, 'total_tokens': 28}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-7bdaa992-19c9-4f0d-9a0c-1f326bc992d4-0')"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# We can use `.with_config(configurable={\"llm\": \"openai\"})` to specify an llm to use\n",
    "chain.with_config(configurable={\"llm\": \"openai\"}).invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "42647fb7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\\n\\nHow's that? I tried to come up with a simple, silly pun-based joke about bears. Puns and wordplay are a common way to create humorous bear jokes. Let me know if you'd like to hear another one!\", response_metadata={'id': 'msg_01BZvbmnEPGBtcxRWETCHkct', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 80}}, id='run-59b6ee44-a1cd-41b8-a026-28ee67cdd718-0')"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# If we use the `default_key` then it uses the default\n",
    "chain.with_config(configurable={\"llm\": \"anthropic\"}).invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9134559",
   "metadata": {},
   "source": [
    "### With Prompts\n",
    "\n",
    "We can do a similar thing, but alternate between prompts\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9f6a7c6c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Here's a bear joke for you:\\n\\nWhy don't bears wear socks? \\nBecause they have bear feet!\", response_metadata={'id': 'msg_01DtM1cssjNFZYgeS3gMZ49H', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 28}}, id='run-8199af7d-ea31-443d-b064-483693f2e0a1-0')"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "llm = ChatAnthropic(model=\"claude-3-haiku-20240307\", temperature=0)\n",
    "prompt = PromptTemplate.from_template(\n",
    "    \"Tell me a joke about {topic}\"\n",
    ").configurable_alternatives(\n",
    "    # This gives this field an id\n",
    "    # When configuring the end runnable, we can then use this id to configure this field\n",
    "    ConfigurableField(id=\"prompt\"),\n",
    "    # This sets a default_key.\n",
    "    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
    "    default_key=\"joke\",\n",
    "    # This adds a new option, with name `poem`\n",
    "    poem=PromptTemplate.from_template(\"Write a short poem about {topic}\"),\n",
    "    # You can add more configuration options here\n",
    ")\n",
    "chain = prompt | llm\n",
    "\n",
    "# By default it will write a joke\n",
    "chain.invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "927297a1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Here is a short poem about bears:\\n\\nMajestic bears, strong and true,\\nRoaming the forests, wild and free.\\nPowerful paws, fur soft and brown,\\nCommanding respect, nature's crown.\\n\\nForaging for berries, fishing streams,\\nProtecting their young, fierce and keen.\\nMighty bears, a sight to behold,\\nGuardians of the wilderness, untold.\\n\\nIn the wild they reign supreme,\\nEmbodying nature's grand theme.\\nBears, a symbol of strength and grace,\\nCaptivating all who see their face.\", response_metadata={'id': 'msg_01Wck3qPxrjURtutvtodaJFn', 'model': 'claude-3-haiku-20240307', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 13, 'output_tokens': 134}}, id='run-69414a1e-51d7-4bec-a307-b34b7d61025e-0')"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# We can configure it write a poem\n",
    "chain.with_config(configurable={\"prompt\": \"poem\"}).invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c77124e",
   "metadata": {},
   "source": [
    "### With Prompts and LLMs\n",
    "\n",
    "We can also have multiple things configurable!\n",
    "Here's an example doing that with both prompts and LLMs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "97538c23",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"In the forest deep and wide,\\nBears roam with grace and pride.\\nWith fur as dark as night,\\nThey rule the land with all their might.\\n\\nIn winter's chill, they hibernate,\\nIn spring they emerge, hungry and great.\\nWith claws sharp and eyes so keen,\\nThey hunt for food, fierce and lean.\\n\\nBut beneath their tough exterior,\\nLies a gentle heart, warm and superior.\\nThey love their cubs with all their might,\\nProtecting them through day and night.\\n\\nSo let us admire these majestic creatures,\\nIn awe of their strength and features.\\nFor in the wild, they reign supreme,\\nThe mighty bears, a timeless dream.\", response_metadata={'token_usage': {'completion_tokens': 133, 'prompt_tokens': 13, 'total_tokens': 146}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-5eec0b96-d580-49fd-ac4e-e32a0803b49b-0')"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "llm = ChatAnthropic(\n",
    "    model=\"claude-3-haiku-20240307\", temperature=0\n",
    ").configurable_alternatives(\n",
    "    # This gives this field an id\n",
    "    # When configuring the end runnable, we can then use this id to configure this field\n",
    "    ConfigurableField(id=\"llm\"),\n",
    "    # This sets a default_key.\n",
    "    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
    "    default_key=\"anthropic\",\n",
    "    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`\n",
    "    openai=ChatOpenAI(),\n",
    "    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model=\"gpt-4\")`\n",
    "    gpt4=ChatOpenAI(model=\"gpt-4\"),\n",
    "    # You can add more configuration options here\n",
    ")\n",
    "prompt = PromptTemplate.from_template(\n",
    "    \"Tell me a joke about {topic}\"\n",
    ").configurable_alternatives(\n",
    "    # This gives this field an id\n",
    "    # When configuring the end runnable, we can then use this id to configure this field\n",
    "    ConfigurableField(id=\"prompt\"),\n",
    "    # This sets a default_key.\n",
    "    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used\n",
    "    default_key=\"joke\",\n",
    "    # This adds a new option, with name `poem`\n",
    "    poem=PromptTemplate.from_template(\"Write a short poem about {topic}\"),\n",
    "    # You can add more configuration options here\n",
    ")\n",
    "chain = prompt | llm\n",
    "\n",
    "# We can configure it write a poem with OpenAI\n",
    "chain.with_config(configurable={\"prompt\": \"poem\", \"llm\": \"openai\"}).invoke(\n",
    "    {\"topic\": \"bears\"}\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "e4ee9fbc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Why don't bears wear shoes?\\n\\nBecause they have bear feet!\", response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 13, 'total_tokens': 26}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-c1b14c9c-4988-49b8-9363-15bfd479973a-0')"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# We can always just configure only one if we want\n",
    "chain.with_config(configurable={\"llm\": \"openai\"}).invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02fc4841",
   "metadata": {},
   "source": [
    "### Saving configurations\n",
    "\n",
    "We can also easily save configured chains as their own objects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "5cf53202",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content=\"Why did the bear break up with his girlfriend? \\nBecause he couldn't bear the relationship anymore!\", response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 13, 'total_tokens': 33}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_c2295e73ad', 'finish_reason': 'stop', 'logprobs': None}, id='run-391ebd55-9137-458b-9a11-97acaff6a892-0')"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "openai_joke = chain.with_config(configurable={\"llm\": \"openai\"})\n",
    "\n",
    "openai_joke.invoke({\"topic\": \"bears\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76702b0e",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "You now know how to configure a chain's internal steps at runtime.\n",
    "\n",
    "To learn more, see the other how-to guides on runnables in this section, including:\n",
    "\n",
    "- Using [.bind()](/docs/how_to/binding) as a simpler way to set a runnable's runtime parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a43e3b70",
   "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.9.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}