Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure OpenAI notebook #191

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
346 changes: 346 additions & 0 deletions weaviate-features/hybrid-search/hybrid_search_azure_openai.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Hybrid Search with Azure OpenAI\n",
"\n",
"This recipe will show you how to run hybrid search with embeddings from Azure OpenAI."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Requirements\n",
"\n",
"1. Weaviate cluster\n",
" 1. You can create a 14-day free sandbox on [WCD](https://console.weaviate.cloud/)\n",
" 2. [Embedded Weaviate](https://weaviate.io/developers/weaviate/installation/embedded)\n",
" 3. [Local deployment](https://weaviate.io/developers/weaviate/installation/docker-compose#starter-docker-compose-file)\n",
" 4. [Other options](https://weaviate.io/developers/weaviate/installation)\n",
"\n",
"2. Azure API key. Grab one [here](https://portal.azure.com/)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import Dependencies, Libraries, and Keys"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install --q weaviate-client"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import weaviate\n",
"from weaviate.classes.init import Auth\n",
"import weaviate.classes.config as wc\n",
"from weaviate.embedded import EmbeddedOptions\n",
"import weaviate.classes.query as wq\n",
"\n",
"\n",
"import os\n",
"import requests\n",
"import json"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connect to Weaviate\n",
"\n",
"Only choose one option from the below."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Weaviate Cloud Deployment**"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"# WCD_URL = os.environ[\"WEAVIATE_URL\"] # Replace with your Weaviate cluster URL\n",
"# WCD_AUTH_KEY = os.environ[\"WEAVIATE_AUTH\"] # Replace with your cluster auth key\n",
"# AZURE_KEY = os.environ[\"AZURE_API_KEY\"] # Replace with your Azure key\n",
"\n",
"# Weaviate Cloud Deployment\n",
"client = weaviate.connect_to_wcs(\n",
" cluster_url=WCD_URL,\n",
" auth_credentials=weaviate.auth.AuthApiKey(WCD_AUTH_KEY),\n",
" headers={ \"X-Azure-Api-Key\": AZURE_KEY}\n",
")\n",
"\n",
"print(client.is_ready())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Embedded Weaviate**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# AZURE_KEY = os.environ[\"AZURE_API_KEY\"] # Replace with your Azure key\n",
"\n",
"# client = weaviate.WeaviateClient(\n",
"# embedded_options=EmbeddedOptions(\n",
"# version=\"1.28.2\",\n",
"# additional_env_vars={\n",
"# \"ENABLE_MODULES\": \"text2vec-openai\"\n",
"# }),\n",
"# additional_headers={\n",
"# \"X-Azure-Api-Key\": AZURE_KEY\n",
"# }\n",
"# )\n",
"\n",
"# client.connect()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Local Deployment**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# AZURE_KEY = os.environ[\"AZURE_API_KEY\"] # Replace with your Azure key\n",
"\n",
"# client = weaviate.connect_to_local(\n",
"# headers={\n",
"# \"X-Azure-Api-Key\": AZURE_KEY\n",
"# }\n",
"# )\n",
"# print(client.is_ready())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Create a collection\n",
"> Collection stores your data and vector embeddings."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Successfully created collection: JeopardyQuestion.\n"
]
}
],
"source": [
"# Note: This will delete your data stored in \"JeopardyQuestion\" and\n",
"# it will require you to re-import again.\n",
"\n",
"# Delete the collection if it already exists\n",
"if (client.collections.exists(\"JeopardyQuestion\")):\n",
" client.collections.delete(\"JeopardyQuestion\")\n",
"\n",
"client.collections.create(\n",
" name=\"JeopardyQuestion\",\n",
"\n",
" vectorizer_config=wc.Configure.Vectorizer.text2vec_azure_openai(\n",
" resource_name=\"azure-xyz\", # name of your resource group\n",
" deployment_id=\"fc...\" # subscription id\n",
" ),\n",
"\n",
" properties=[ # defining properties (data schema) is optional\n",
" wc.Property(name=\"Question\", data_type=wc.DataType.TEXT), \n",
" wc.Property(name=\"Answer\", data_type=wc.DataType.TEXT),\n",
" wc.Property(name=\"Category\", data_type=wc.DataType.TEXT) \n",
" ]\n",
")\n",
"\n",
"print(\"Successfully created collection: JeopardyQuestion.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import the Data"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"ename": "WeaviateInsertManyAllFailedError",
"evalue": "Every object failed during insertion. Here is the set of all errors: connection to: Azure OpenAI API failed with status: 401 error: Access denied due to invalid subscription key or wrong API endpoint. Make sure to provide a valid key for an active subscription and use a correct regional API endpoint for your resource.",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mWeaviateInsertManyAllFailedError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[7], line 9\u001b[0m\n\u001b[1;32m 6\u001b[0m jeopardy \u001b[38;5;241m=\u001b[39m client\u001b[38;5;241m.\u001b[39mcollections\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mJeopardyQuestion\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 8\u001b[0m \u001b[38;5;66;03m# Insert data objects\u001b[39;00m\n\u001b[0;32m----> 9\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[43mjeopardy\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minsert_many\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 11\u001b[0m \u001b[38;5;66;03m# Note, the `data` array contains 10 objects, which is great to call insert_many with.\u001b[39;00m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;66;03m# However, if you have a milion objects to insert, then you should spit them into smaller batches (i.e. 100-1000 per insert)\u001b[39;00m\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (response\u001b[38;5;241m.\u001b[39mhas_errors):\n",
"File \u001b[0;32m/usr/local/lib/python3.11/site-packages/weaviate/syncify.py:23\u001b[0m, in \u001b[0;36mconvert.<locals>.sync_method\u001b[0;34m(self, __new_name, *args, **kwargs)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(method) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 21\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21msync_method\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, __new_name\u001b[38;5;241m=\u001b[39mnew_name, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 22\u001b[0m async_func \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mcls\u001b[39m, __new_name)\n\u001b[0;32m---> 23\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_EventLoopSingleton\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_instance\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun_until_complete\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 24\u001b[0m \u001b[43m \u001b[49m\u001b[43masync_func\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\n\u001b[1;32m 25\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/usr/local/lib/python3.11/site-packages/weaviate/event_loop.py:42\u001b[0m, in \u001b[0;36m_EventLoop.run_until_complete\u001b[0;34m(self, f, *args, **kwargs)\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m WeaviateClosedClientError()\n\u001b[1;32m 41\u001b[0m fut \u001b[38;5;241m=\u001b[39m asyncio\u001b[38;5;241m.\u001b[39mrun_coroutine_threadsafe(f(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mloop)\n\u001b[0;32m---> 42\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfut\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mresult\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/usr/local/Cellar/[email protected]/3.11.6/Frameworks/Python.framework/Versions/3.11/lib/python3.11/concurrent/futures/_base.py:456\u001b[0m, in \u001b[0;36mFuture.result\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 454\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m CancelledError()\n\u001b[1;32m 455\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_state \u001b[38;5;241m==\u001b[39m FINISHED:\n\u001b[0;32m--> 456\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__get_result\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 457\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 458\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTimeoutError\u001b[39;00m()\n",
"File \u001b[0;32m/usr/local/Cellar/[email protected]/3.11.6/Frameworks/Python.framework/Versions/3.11/lib/python3.11/concurrent/futures/_base.py:401\u001b[0m, in \u001b[0;36mFuture.__get_result\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 399\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception:\n\u001b[1;32m 400\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 401\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_exception\n\u001b[1;32m 402\u001b[0m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[1;32m 403\u001b[0m \u001b[38;5;66;03m# Break a reference cycle with the exception in self._exception\u001b[39;00m\n\u001b[1;32m 404\u001b[0m \u001b[38;5;28mself\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n",
"File \u001b[0;32m/usr/local/lib/python3.11/site-packages/weaviate/collections/data/data.py:386\u001b[0m, in \u001b[0;36m_DataCollectionAsync.insert_many\u001b[0;34m(self, objects)\u001b[0m\n\u001b[1;32m 346\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Insert multiple objects into the collection.\u001b[39;00m\n\u001b[1;32m 347\u001b[0m \n\u001b[1;32m 348\u001b[0m \u001b[38;5;124;03mArguments:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 360\u001b[0m \u001b[38;5;124;03m If every object in the batch fails to be inserted. The exception message contains details about the failure.\u001b[39;00m\n\u001b[1;32m 361\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 362\u001b[0m objs \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m 363\u001b[0m (\n\u001b[1;32m 364\u001b[0m _BatchObject(\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 384\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m idx, obj \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(objects)\n\u001b[1;32m 385\u001b[0m ]\n\u001b[0;32m--> 386\u001b[0m res \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mawait\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_batch_grpc\u001b[38;5;241m.\u001b[39mobjects(objs, timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_connection\u001b[38;5;241m.\u001b[39mtimeout_config\u001b[38;5;241m.\u001b[39minsert)\n\u001b[1;32m 387\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (n_obj_errs \u001b[38;5;241m:=\u001b[39m \u001b[38;5;28mlen\u001b[39m(res\u001b[38;5;241m.\u001b[39merrors)) \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m 388\u001b[0m logger\u001b[38;5;241m.\u001b[39merror(\n\u001b[1;32m 389\u001b[0m {\n\u001b[1;32m 390\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessage\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to send \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mn_obj_errs\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m objects in a batch of \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(objs)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m. Please inspect the errors variable of the returned object for more information.\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 391\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124merrors\u001b[39m\u001b[38;5;124m\"\u001b[39m: res\u001b[38;5;241m.\u001b[39merrors,\n\u001b[1;32m 392\u001b[0m }\n\u001b[1;32m 393\u001b[0m )\n",
"File \u001b[0;32m/usr/local/lib/python3.11/site-packages/weaviate/collections/batch/grpc_batch_objects.py:105\u001b[0m, in \u001b[0;36m_BatchGRPC.objects\u001b[0;34m(self, objects, timeout)\u001b[0m\n\u001b[1;32m 101\u001b[0m elapsed_time \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime() \u001b[38;5;241m-\u001b[39m start\n\u001b[1;32m 103\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(errors) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlen\u001b[39m(weaviate_objs):\n\u001b[1;32m 104\u001b[0m \u001b[38;5;66;03m# Escape sequence (backslash) not allowed in expression portion of f-string prior to Python 3.12: pylance\u001b[39;00m\n\u001b[0;32m--> 105\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m WeaviateInsertManyAllFailedError(\n\u001b[1;32m 106\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mHere is the set of all errors: \u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\n\u001b[1;32m 107\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;241m.\u001b[39mjoin(err \u001b[38;5;28;01mfor\u001b[39;00m err \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mset\u001b[39m(errors\u001b[38;5;241m.\u001b[39mvalues()))\n\u001b[1;32m 108\u001b[0m )\n\u001b[1;32m 109\u001b[0m )\n\u001b[1;32m 111\u001b[0m all_responses: List[Union[uuid_package\u001b[38;5;241m.\u001b[39mUUID, ErrorObject]] \u001b[38;5;241m=\u001b[39m cast(\n\u001b[1;32m 112\u001b[0m List[Union[uuid_package\u001b[38;5;241m.\u001b[39mUUID, ErrorObject]], \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mrange\u001b[39m(\u001b[38;5;28mlen\u001b[39m(weaviate_objs)))\n\u001b[1;32m 113\u001b[0m )\n\u001b[1;32m 114\u001b[0m return_success: Dict[\u001b[38;5;28mint\u001b[39m, uuid_package\u001b[38;5;241m.\u001b[39mUUID] \u001b[38;5;241m=\u001b[39m {}\n",
"\u001b[0;31mWeaviateInsertManyAllFailedError\u001b[0m: Every object failed during insertion. Here is the set of all errors: connection to: Azure OpenAI API failed with status: 401 error: Access denied due to invalid subscription key or wrong API endpoint. Make sure to provide a valid key for an active subscription and use a correct regional API endpoint for your resource."
]
}
],
"source": [
"url = 'https://raw.githubusercontent.com/weaviate/weaviate-examples/main/jeopardy_small_dataset/jeopardy_tiny.json'\n",
"resp = requests.get(url)\n",
"data = json.loads(resp.text)\n",
"\n",
"# Get a collection object for \"JeopardyQuestion\"\n",
"jeopardy = client.collections.get(\"JeopardyQuestion\")\n",
"\n",
"# Insert data objects\n",
"response = jeopardy.data.insert_many(data)\n",
"\n",
"# Note, the `data` array contains 10 objects, which is great to call insert_many with.\n",
"# However, if you have a milion objects to insert, then you should spit them into smaller batches (i.e. 100-1000 per insert)\n",
"\n",
"if (response.has_errors):\n",
" print(response.errors)\n",
"else:\n",
" print(\"Insert complete.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Hybrid Search"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `alpha` parameter determines the weight given to the sparse and dense search methods. `alpha = 0` is pure sparse (bm25) search, whereas `alpha = 1` is pure dense (vector) search. \n",
"\n",
"Alpha is an optional parameter. The default is set to `0.75`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Hybrid Search only\n",
"\n",
"The below query is finding Jeopardy questions about animals and is limiting the output to only two results. Notice `alpha` is set to `0.80`, which means it is weighing the vector search results more than bm25. If you were to set `alpha = 0.25`, you would get different results. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = jeopardy.query.hybrid(\n",
" query=\"northern beast\",\n",
" query_properties=[\"question\"],\n",
" alpha=0.8,\n",
" limit=3\n",
")\n",
"\n",
"for item in response.objects:\n",
" print(\"ID:\", item.uuid)\n",
" print(\"Data:\", json.dumps(item.properties, indent=2), \"\\n\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Hybrid Search with a `where` filter\n",
"\n",
"Find Jeopardy questions about elephants, where the category is set to Animals."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = jeopardy.query.hybrid(\n",
" query=\"northern beast\",\n",
" alpha=0.8,\n",
" filters=wq.Filter.by_property(\"category\").equal(\"Animals\"),\n",
" limit=3\n",
")\n",
"\n",
"for item in response.objects:\n",
" print(\"ID:\", item.uuid)\n",
" print(\"Data:\", json.dumps(item.properties, indent=2), \"\\n\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.6"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Loading