{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Guided Conversation Artifact\n",
    "This notebook explores one of our core modular components or plugins, the Artifact.\n",
    "\n",
    "The artifact is a form, or a type of working memory for the agent. We implement it using a Pydantic BaseModel. As the conversation creator, you can define an arbitrary BaseModel that includes the fields you want the agent to fill out during the conversation. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Motivating Example - Collecting Information from a User\n",
    "\n",
    "Let's setup an artifact where the goal is to collect information about a customer's issue with a service."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "from pydantic import BaseModel, Field, conlist\n",
    "\n",
    "\n",
    "class Issue(BaseModel):\n",
    "    incident_type: Literal[\"Service Outage\", \"Degradation\", \"Billing\", \"Security\", \"Data Loss\", \"Other\"] = Field(\n",
    "        description=\"A high level type describing the incident.\"\n",
    "    )\n",
    "    description: str = Field(description=\"A detailed description of what is going wrong.\")\n",
    "    affected_services: conlist(str, min_length=0) = Field(description=\"The services affected by the incident.\")\n",
    "\n",
    "\n",
    "class OutageArtifact(BaseModel):\n",
    "    name: str = Field(description=\"How to address the customer.\")\n",
    "    company: str = Field(description=\"The company the customer works for.\")\n",
    "    role: str = Field(description=\"The role of the customer.\")\n",
    "    email: str = Field(description=\"The best email to contact the customer.\", pattern=r\"^/^.+@.+$/$\")\n",
    "    phone: str = Field(description=\"The best phone number to contact the customer.\", pattern=r\"^\\d{3}-\\d{3}-\\d{4}$\")\n",
    "\n",
    "    incident_start: int = Field(\n",
    "        description=\"About how many hours ago the incident started.\",\n",
    "    )\n",
    "    incident_end: int = Field(\n",
    "        description=\"About how many hours ago the incident ended. If the incident is ongoing, set this to 0.\",\n",
    "    )\n",
    "\n",
    "    issues: conlist(Issue, min_length=1) = Field(description=\"The issues the customer is experiencing.\")\n",
    "    additional_comments: conlist(str, min_length=0) = Field(\"Any additional comments the customer has.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's initialize the artifact as a standalone module.\n",
    "\n",
    "It requires a Kernel and LLM Service, alongside a Conversation object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel import Kernel\n",
    "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n",
    "\n",
    "from guided_conversation.plugins.artifact import Artifact\n",
    "from guided_conversation.utils.conversation_helpers import Conversation\n",
    "\n",
    "kernel = Kernel()\n",
    "service_id = \"artifact_chat_completion\"\n",
    "chat_service = AzureChatCompletion(\n",
    "    service_id=service_id,\n",
    "    deployment_name=\"gpt-4o-2024-05-13\",\n",
    "    api_version=\"2024-05-01-preview\",\n",
    ")\n",
    "kernel.add_service(chat_service)\n",
    "\n",
    "# Initialize the artifact\n",
    "artifact = Artifact(kernel, service_id, OutageArtifact, max_artifact_field_retries=2)\n",
    "conversation = Conversation()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To power the Artifact's ability to automatically fix issues, we provide the conversation history as additional context."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.contents import AuthorRole, ChatMessageContent\n",
    "\n",
    "conversation.add_messages(\n",
    "    ChatMessageContent(\n",
    "        role=AuthorRole.ASSISTANT,\n",
    "        content=\"Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\",\n",
    "    )\n",
    ")\n",
    "conversation.add_messages(\n",
    "    ChatMessageContent(\n",
    "        role=AuthorRole.USER,\n",
    "        content=\"Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\",\n",
    "    )\n",
    ")\n",
    "\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"name\",\n",
    "    field_value=\"Jane Doe\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)\n",
    "\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"company\",\n",
    "    field_value=\"Contoso\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)\n",
    "\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"role\",\n",
    "    field_value=\"Database Administrator\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see how the artifact was updated with these valid updates and the resulting conversation messages that were generated.\n",
    "\n",
    "The Artifact creates messages whenever a field is updated for use in downstream agents like the main GuidedConversation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Conversation up to this point:\n",
      "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n",
      "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n",
      "Assistant updated name to Jane Doe\n",
      "Assistant updated company to Contoso\n",
      "Assistant updated role to Database Administrator\n",
      "\n",
      "Current state of the artifact:\n",
      "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'email': 'Unanswered', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': 'Unanswered', 'additional_comments': 'Unanswered'}\n"
     ]
    }
   ],
   "source": [
    "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n",
    "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next we test an invalid update on a field with a regex. The agent should not update the artifact and\n",
    "instead resume the conversation because the provided email is incomplete."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error updating field email: 1 validation error for Artifact\n",
      "email\n",
      "  String should match pattern '^/^.+@.+$/$|Unanswered' [type=string_pattern_mismatch, input_value='jdoe', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.8/v/string_pattern_mismatch. Retrying...\n"
     ]
    }
   ],
   "source": [
    "conversation.add_messages(\n",
    "    ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"What is the best email to contact you at?\")\n",
    ")\n",
    "conversation.add_messages(ChatMessageContent(role=AuthorRole.USER, content=\"my email is jdoe\"))\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"email\",\n",
    "    field_value=\"jdoe\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the agent returned success, but did make an update (as shown by not generating a conversation message indicating such),\n",
    "then we implicitly assume the agent has resumed the conversation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Conversation up to this point:\n",
      "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n",
      "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n",
      "Assistant updated name to Jane Doe\n",
      "Assistant updated company to Contoso\n",
      "Assistant updated role to Database Administrator\n",
      "Assistant: What is the best email to contact you at?\n",
      "None: my email is jdoe\n"
     ]
    }
   ],
   "source": [
    "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see what happens if we keep trying to update that failed field."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error updating field email: 1 validation error for Artifact\n",
      "email\n",
      "  String should match pattern '^/^.+@.+$/$|Unanswered' [type=string_pattern_mismatch, input_value='jdoe', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.8/v/string_pattern_mismatch. Retrying...\n",
      "Updating field email has failed too many times. Skipping.\n"
     ]
    }
   ],
   "source": [
    "result = await artifact.update_artifact(\n",
    "    field_name=\"email\",\n",
    "    field_value=\"jdoe\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "\n",
    "# And again\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"email\",\n",
    "    field_value=\"jdoe\",\n",
    "    conversation=conversation,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we look at the current state of the artifact, we should see that the email has been removed\n",
    "since it has now failed 3 times which is greater than the max_artifact_field_retries parameter we set\n",
    "when we instantiated the artifact."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'Jane Doe',\n",
       " 'company': 'Contoso',\n",
       " 'role': 'Database Administrator',\n",
       " 'phone': 'Unanswered',\n",
       " 'incident_start': 'Unanswered',\n",
       " 'incident_end': 'Unanswered',\n",
       " 'issues': 'Unanswered',\n",
       " 'additional_comments': 'Unanswered'}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "artifact.get_artifact_for_prompt()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's move on to trying to update a more complex field: the issues field."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Conversation up to this point:\n",
      "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n",
      "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n",
      "Assistant updated name to Jane Doe\n",
      "Assistant updated company to Contoso\n",
      "Assistant updated role to Database Administrator\n",
      "Assistant: What is the best email to contact you at?\n",
      "None: my email is jdoe\n",
      "Assistant: Can you tell me about the issues you're experiencing?\n",
      "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n",
      "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n",
      "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n",
      "\n",
      "Current state of the artifact:\n",
      "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}], 'additional_comments': 'Unanswered'}\n"
     ]
    }
   ],
   "source": [
    "conversation.add_messages(\n",
    "    ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"Can you tell me about the issues you're experiencing?\")\n",
    ")\n",
    "conversation.add_messages(\n",
    "    ChatMessageContent(\n",
    "        role=AuthorRole.USER,\n",
    "        content=\"\"\"The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n",
    "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\"\"\",\n",
    "    )\n",
    ")\n",
    "\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"issues\",\n",
    "    field_value=[\n",
    "        {\n",
    "            \"incident_type\": \"Degradation\",\n",
    "            \"description\": \"\"\"The latency of accessing the customer's database service has increased by 200% in the \\\n",
    "last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\"\"\",\n",
    "            \"affected_services\": [\"Database Service\", \"Database Management Portal\"],\n",
    "        }\n",
    "    ],\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)\n",
    "\n",
    "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n",
    "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To add another affected service, we can need to update the issues field with the new value again.\n",
    "The obvious con of this approach is that the model generating the field_value has to regenerate the entire field_value.\n",
    "However, the pro is that keeps the available tools simple for the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Conversation up to this point:\n",
      "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n",
      "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n",
      "Assistant updated name to Jane Doe\n",
      "Assistant updated company to Contoso\n",
      "Assistant updated role to Database Administrator\n",
      "Assistant: What is the best email to contact you at?\n",
      "None: my email is jdoe\n",
      "Assistant: Can you tell me about the issues you're experiencing?\n",
      "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n",
      "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n",
      "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n",
      "Assistant: Is there anything else you'd like to add about the issues you're experiencing?\n",
      "None: Yes another thing that is effected is access to billing information is very slow.\n",
      "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}]\n",
      "\n",
      "Current state of the artifact:\n",
      "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n"
     ]
    }
   ],
   "source": [
    "conversation.add_messages(\n",
    "    ChatMessageContent(\n",
    "        role=AuthorRole.ASSISTANT,\n",
    "        content=\"Is there anything else you'd like to add about the issues you're experiencing?\",\n",
    "    )\n",
    ")\n",
    "conversation.add_messages(\n",
    "    ChatMessageContent(\n",
    "        role=AuthorRole.USER,\n",
    "        content=\"Yes another thing that is effected is access to billing information is very slow.\",\n",
    "    )\n",
    ")\n",
    "\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"issues\",\n",
    "    field_value=[\n",
    "        {\n",
    "            \"incident_type\": \"Degradation\",\n",
    "            \"description\": \"\"\"The latency of accessing the customer's database service has increased by 200% in the \\\n",
    "last 24 hours, even on a fresh instance. They also report timeouts when trying to access the \\\n",
    "management portal and slowdowns in the access to billing information.\"\"\",\n",
    "            \"affected_services\": [\"Database Service\", \"Database Management Portal\", \"Billing portal\"],\n",
    "        },\n",
    "    ],\n",
    "    conversation=conversation,\n",
    ")\n",
    "conversation.add_messages(result.messages)\n",
    "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n",
    "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see what happens if we try to update a field that is not in the artifact."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Was the update successful? False\n",
      "Conversation up to this point:\n",
      "Assistant: Hello! I'm here to help you with your issue. Can you tell me your name, company, and role?\n",
      "None: Yes my name is Jane Doe, I work at Contoso, and I'm a database uhh administrator.\n",
      "Assistant updated name to Jane Doe\n",
      "Assistant updated company to Contoso\n",
      "Assistant updated role to Database Administrator\n",
      "Assistant: What is the best email to contact you at?\n",
      "None: my email is jdoe\n",
      "Assistant: Can you tell me about the issues you're experiencing?\n",
      "None: The latency of accessing our database service has increased by 200\\% in the last 24 hours, \n",
      "even on a fresh instance. Additionally, we're seeing a lot of timeouts when trying to access the management portal.\n",
      "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal.\", 'affected_services': ['Database Service', 'Database Management Portal']}]\n",
      "Assistant: Is there anything else you'd like to add about the issues you're experiencing?\n",
      "None: Yes another thing that is effected is access to billing information is very slow.\n",
      "Assistant updated issues to [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}]\n",
      "\n",
      "Current state of the artifact:\n",
      "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 'Unanswered', 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n"
     ]
    }
   ],
   "source": [
    "result = await artifact.update_artifact(\n",
    "    field_name=\"not_a_field\",\n",
    "    field_value=\"some value\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "# We should see that the update was immediately unsuccessful, but the conversation and artifact should remain unchanged.\n",
    "print(f\"Was the update successful? {result.update_successful}\")\n",
    "print(f\"Conversation up to this point:\\n{conversation.get_repr_for_prompt()}\\n\")\n",
    "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's see what happens if we try to update a field with the incorrect type, but the correct information was provided in the conversation. \n",
    "We should see the agent correctly updated the field correctly as an integer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Error updating field incident_start: 2 validation errors for Artifact\n",
      "incident_start.int\n",
      "  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='3 hours', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.8/v/int_parsing\n",
      "incident_start.literal['Unanswered']\n",
      "  Input should be 'Unanswered' [type=literal_error, input_value='3 hours', input_type=str]\n",
      "    For further information visit https://errors.pydantic.dev/2.8/v/literal_error. Retrying...\n",
      "Agent failed to fix field incident_start. Retrying...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Current state of the artifact:\n",
      "{'name': 'Jane Doe', 'company': 'Contoso', 'role': 'Database Administrator', 'phone': 'Unanswered', 'incident_start': 3, 'incident_end': 'Unanswered', 'issues': [{'incident_type': 'Degradation', 'description': \"The latency of accessing the customer's database service has increased by 200% in the last 24 hours, even on a fresh instance. They also report timeouts when trying to access the management portal and slowdowns in the access to billing information.\", 'affected_services': ['Database Service', 'Database Management Portal', 'Billing portal']}], 'additional_comments': 'Unanswered'}\n"
     ]
    }
   ],
   "source": [
    "conversation.add_messages(\n",
    "    ChatMessageContent(role=AuthorRole.ASSISTANT, content=\"How many hours ago did the incident start?\")\n",
    ")\n",
    "conversation.add_messages(ChatMessageContent(role=AuthorRole.USER, content=\"about 3 hours ago\"))\n",
    "result = await artifact.update_artifact(\n",
    "    field_name=\"incident_start\",\n",
    "    field_value=\"3 hours\",\n",
    "    conversation=conversation,\n",
    ")\n",
    "\n",
    "print(f\"Current state of the artifact:\\n{artifact.get_artifact_for_prompt()}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}