diff --git a/notebooks/vertex_genai/solutions/deployment_adk_cloudrun.ipynb b/notebooks/vertex_genai/solutions/deployment_adk_cloudrun.ipynb new file mode 100644 index 000000000..d6f51da10 --- /dev/null +++ b/notebooks/vertex_genai/solutions/deployment_adk_cloudrun.ipynb @@ -0,0 +1,681 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2fdb77ea71896541", + "metadata": {}, + "source": [ + "# Deploying ADK Agent on Cloud Run" + ] + }, + { + "cell_type": "markdown", + "id": "d4b17ab0-673c-4a1c-9816-1414c60fd926", + "metadata": { + "tags": [] + }, + "source": [ + "## Overview\n", + "\n", + "This notebook guides you through deploying and managing a conversational AI agent to **Cloud Run**. \n", + "\n", + "## Learning Goals\n", + "\n", + "By the end of this notebook, you will understand how to:\n", + "* Set up required configuration and deploy ADK **Agents** to **Cloud Run**.\n", + "* Set up required configuration and deploy ADK **Agents** to **Cloud Run** using **custom container**.\n", + "* Configure the ADK **Cloud Run** and **Cloud SQL** to store use sessions." + ] + }, + { + "cell_type": "markdown", + "id": "31c5d860-0945-4799-b2ba-74298c87ee3b", + "metadata": {}, + "source": [ + "#### Cloud Run\n", + "Cloud Run is a managed auto-scaling compute platform on Google Cloud that enables you to run your agent as a container-based application.\n", + "[Learn more about deploying your agent to Cloud Run.](https://google.github.io/adk-docs/deploy/cloud-run/)" + ] + }, + { + "cell_type": "markdown", + "id": "c277b2c7cbd4edfd", + "metadata": {}, + "source": [ + "### Required imports" + ] + }, + { + "cell_type": "code", + "id": "09d0fe85-302a-4ac6-afcb-360bb7764c3d", + "metadata": { + "tags": [] + }, + "source": [ + "import importlib\n", + "import json\n", + "import os\n", + "import warnings\n", + "\n", + "# Ignore all warnings\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "import logging\n", + "\n", + "logging.basicConfig(level=logging.ERROR)" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "id": "9e7b1b3e-6b91-42f4-9550-fe027efb1988", + "metadata": { + "tags": [] + }, + "source": [ + "LOCATION = \"us-central1\"\n", + "os.environ[\"GOOGLE_CLOUD_LOCATION\"] = LOCATION\n", + "os.environ[\"GOOGLE_GENAI_USE_VERTEXAI\"] = \"TRUE\" # Use Vertex AI API\n", + "\n", + "PROJECT = !gcloud config list --format 'value(core.project)'\n", + "PROJECT = PROJECT[0]\n", + "BUCKET_NAME = f\"agent-deployment-{PROJECT}-bucket\"\n", + "BUCKET_URI = f\"gs://{BUCKET_NAME}\"\n", + "\n", + "ADK_APP_NAME = \"adk-demo-app\"\n", + "CLOUDRUN_SERVICE_NAME = \"adk-cloudrun-demo\"\n", + "\n", + "os.environ[\"GOOGLE_CLOUD_PROJECT\"] = PROJECT\n", + "os.environ[\"SERVICE_NAME\"] = CLOUDRUN_SERVICE_NAME\n", + "os.environ[\"LOCATION\"] = LOCATION\n", + "os.environ[\"APP_NAME\"] = ADK_APP_NAME\n", + "os.environ[\"AGENT_PATH\"] = \"./adk_agents/agent1_weather_lookup/\"" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "id": "ec87bb48-e10d-49f7-97da-4ffc52edd4a4", + "metadata": { + "tags": [] + }, + "source": [ + "%%bash\n", + "echo > adk_agents/.env \"GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION\n", + "GOOGLE_GENAI_USE_VERTEXAI=$GOOGLE_GENAI_USE_VERTEXAI\n", + "\"" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "a2d754cc-3499-4b81-81ec-4ba1f4da7a61", + "metadata": {}, + "source": [ + "## Prepare an Agent files\n", + "\n", + "Here, let's reuse the basic agent files created in [building_agent_with_adk.ipynb](../../../../../../Downloads/building_agent_with_adk.ipynb) notebook.\n", + "\n", + "If you haven't run the notebook, executed the cells below to create files." + ] + }, + { + "cell_type": "code", + "id": "7a289a32-cfd2-48cf-abd5-edbc60c12b2c", + "metadata": { + "tags": [] + }, + "source": [ + "%%writefile ./adk_agents/agent1_weather_lookup/tools.py\n", + "def get_weather(city: str) -> dict:\n", + " \"\"\"Retrieves the current weather report for a specified city.\n", + "\n", + " Args:\n", + " city (str): The name of the city (e.g., \"New York\", \"London\", \"Tokyo\").\n", + "\n", + " Returns:\n", + " dict: A dictionary containing the weather information.\n", + " Includes a 'status' key ('success' or 'error').\n", + " If 'success', includes a 'report' key with weather details.\n", + " If 'error', includes an 'error_message' key.\n", + " \"\"\"\n", + " print(f\"--- Tool: get_weather called for city: {city} ---\") # Log tool execution\n", + " city_normalized = city.lower().replace(\" \", \"\") # Basic normalization\n", + "\n", + " # Mock weather data\n", + " mock_weather_db = {\n", + " \"newyork\": {\"status\": \"success\", \"report\": \"The weather in New York is sunny with a temperature of 25°C.\"},\n", + " \"london\": {\"status\": \"success\", \"report\": \"It's cloudy in London with a temperature of 15°C.\"},\n", + " \"tokyo\": {\"status\": \"success\", \"report\": \"Tokyo is experiencing light rain and a temperature of 18°C.\"},\n", + " }\n", + "\n", + " if city_normalized in mock_weather_db:\n", + " return mock_weather_db[city_normalized]\n", + " else:\n", + " return {\"status\": \"error\", \"error_message\": f\"Sorry, I don't have weather information for '{city}'.\"}" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "code", + "id": "bd0d941f-7aee-4bb7-90d6-d6dbae11181d", + "metadata": { + "tags": [] + }, + "source": [ + "%%writefile ./adk_agents/agent1_weather_lookup/agent.py\n", + "from google.adk.agents import Agent\n", + "MODEL = \"gemini-2.0-flash\"\n", + "\n", + "from .tools import get_weather\n", + "\n", + "root_agent = Agent(\n", + " name=\"weather_agent_v1\",\n", + " model=MODEL, # Can be a string for Gemini or a LiteLlm object\n", + " description=\"Provides weather information for specific cities.\",\n", + " instruction=\"You are a helpful weather assistant. \"\n", + " \"When the user asks for the weather in a specific city, \"\n", + " \"use the 'get_weather' tool to find the information. \"\n", + " \"If the tool returns an error, inform the user politely. \"\n", + " \"If the tool is successful, present the weather report clearly.\",\n", + " tools=[get_weather], # Pass the function directly\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "8a3e13a4-81da-43b0-b673-2591ff98ec23", + "metadata": {}, + "source": [ + "**Checking for the existence of BUCKET. Creating it if it doesn't exist:**" + ] + }, + { + "cell_type": "code", + "id": "826bda76-bba8-4bde-905d-5d05b85022bf", + "metadata": { + "tags": [] + }, + "source": [ + "!gsutil ls $BUCKET_URI || gsutil mb -l $LOCATION $BUCKET_URI" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "78294254-96ba-4ee3-8ccc-33be9c83ef2a", + "metadata": { + "tags": [] + }, + "source": [ + "## Deploy to Google Cloud Run\n", + "Cloud Run is a fully managed platform that enables you to run your code directly on top of Google's scalable infrastructure.\n", + "\n", + "To deploy your agent, you can use either: \n", + " - the **adk deploy** cloud_run command (recommended for Python), \n", + " - or with **gcloud run deploy** command through Cloud Run." + ] + }, + { + "cell_type": "markdown", + "id": "943f8b2b-796b-4bd7-9a46-a55f909d0251", + "metadata": { + "tags": [] + }, + "source": [ + "### Deploy your agent to Cloud Run (with WEB UI enabled)\n", + "\n", + "Now you're ready to deploy your agent to Cloud Run by executing **adk deploy cloud_run** command [adk CLI](https://google.github.io/adk-docs/deploy/cloud-run/#adk-cli).\n", + "This step may take several minutes to finish." + ] + }, + { + "cell_type": "code", + "id": "3385e1f9-183f-4aa8-b207-a7b812225709", + "metadata": { + "tags": [] + }, + "source": [ + "%%bash\n", + "export PATH=$PATH:~/.local/bin\n", + "adk deploy cloud_run \\\n", + "--project=$GOOGLE_CLOUD_PROJECT \\\n", + "--region=$GOOGLE_CLOUD_LOCATION \\\n", + "--service_name=$SERVICE_NAME \\\n", + "--app_name=$APP_NAME \\\n", + "--with_ui \\\n", + "$AGENT_PATH" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "adf3bebe-533e-4b24-aed5-07cc771a0fe5", + "metadata": {}, + "source": "#### After Agent deployment finished we can get Service URL, but using **gcloud run services describe** command:" + }, + { + "cell_type": "code", + "id": "151a3418-8f4b-4936-8386-59652c4c974a", + "metadata": { + "tags": [] + }, + "source": [ + "SERVICE_URL = !gcloud run services describe $SERVICE_NAME \\\n", + " --platform managed \\\n", + " --region $LOCATION \\\n", + " --format \"value(status.url)\"\n", + "\n", + "ADK_AGENT_SERVICE_URL = SERVICE_URL[0]\n", + "print(ADK_AGENT_SERVICE_URL)\n", + "\n", + "os.environ[\"ADK_AGENT_SERVICE_URL\"] = ADK_AGENT_SERVICE_URL" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "238529ac-41fc-4c8e-ada2-96e3ba659bc1", + "metadata": {}, + "source": [ + "### Connect to Cloud Run app via Cloud Shell\n", + "\n", + "\n", + "You have a lot of flexibility when it comes to configuring access to your Cloud Run service. You can even [make it publicly accessible](https://cloud.google.com/run/docs/authenticating/public) if you want to.\n", + "\n", + "However, for this example, let's see how to connect to your Cloud Run app securely from Cloud Shell using a proxy." + ] + }, + { + "cell_type": "markdown", + "id": "ace074ac-da12-4228-a32b-a979bd354e5e", + "metadata": {}, + "source": [ + "Follow these steps to open the app from Cloud Shell.\n", + "1. Run the next cell, copy the output `gcloud run services proxy ...`command.\n", + "2. Open Cloud Shell, paste and run the command.\n", + "3. In Cloud Shell, click the \"Web Preview\" button on the toolbar.\n", + "4. Select \"Preview on port 8080\"\n", + "5. A new browser tab or window will open, displaying your ADK Agent app.\n", + "6. ADK dev UI allows you to interact with your agent, manage sessions, and view execution details directly in the browser.\n", + "7. Select your agent from the dropdown menu.\n", + "8. Type a message and verify that you receive an expected response from your agent.\n", + "9. If you experience any unexpected behavior, check the Cloud Run console logs." + ] + }, + { + "cell_type": "code", + "id": "b73878a1-d214-4f27-89fb-e320ad3f4146", + "metadata": {}, + "source": [ + "print(\n", + " f\"gcloud run services proxy {CLOUDRUN_SERVICE_NAME} --project {PROJECT} --region {LOCATION}\"\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "e3c1d639-87ec-426b-85e2-19ecfecb6831", + "metadata": { + "tags": [] + }, + "source": [ + "## Deploy your agent to Cloud Run using custom container" + ] + }, + { + "cell_type": "markdown", + "id": "e7922caf-be26-459d-b5ab-e9e1486d28b5", + "metadata": {}, + "source": [ + "Alternatively, you can deploy using the standard **gcloud run deploy** command with a custom Dockerfile. This method requires more manual setup compared to the adk command but offers flexibility, particularly if you want to embed your agent within a custom FastAPI application.\n", + "The next file initializes a FastAPI application for ADK agent using get_fast_api_app() from ADK.\n", + "Session service URI and a flag for a web interface configured via environment variables." + ] + }, + { + "cell_type": "code", + "id": "b5340297-d8dd-4f60-ac0d-38efc735d532", + "metadata": { + "tags": [] + }, + "source": [ + "%%writefile \"./adk_agents/main.py\"\n", + "# Copyright 2025 Google LLC\n", + "#\n", + "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# http://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License.\n", + "\n", + "\"\"\"\n", + "This file initializes a FastAPI application for ADK agent\n", + "using get_fast_api_app() from ADK. Session service URI and a flag\n", + "for a web interface configured via environment variables.\n", + "It can then be run using Uvicorn, which listens on a port specified by\n", + "the PORT environment variable or defaults to 8080.\n", + "This approach offers more flexibility, particularly if you want to\n", + "embed ADK Agent within a custom FastAPI application.\n", + "It is used for Cloud Run deployment with standard gcloud run deploy command.\n", + "\"\"\"\n", + "\n", + "import os\n", + "\n", + "import uvicorn\n", + "from dotenv import load_dotenv\n", + "from fastapi import FastAPI\n", + "from google.adk.cli.fast_api import get_fast_api_app\n", + "from google.cloud import logging as google_cloud_logging\n", + "\n", + "\n", + "# Load environment variables from .env file\n", + "load_dotenv()\n", + "\n", + "logging_client = google_cloud_logging.Client()\n", + "logger = logging_client.logger(__name__)\n", + "\n", + "AGENT_DIR = os.path.dirname(os.path.abspath(__file__))\n", + "\n", + "# Get session service URI from environment variables\n", + "session_uri = os.getenv(\"SESSION_SERVICE_URI\", None)\n", + "\n", + "# Get Enable Web interface serving flag from environment variables\n", + "# Set web=True if you intend to serve a web interface, False otherwise\n", + "web_interface_enabled = True #os.getenv(\"SERVE_WEB_INTERFACE\", 'False').lower() in ('true', '1')\n", + "\n", + "# Prepare arguments for get_fast_api_app\n", + "app_args = {\"agents_dir\": AGENT_DIR, \"web\": web_interface_enabled}\n", + "\n", + "# Only include session_service_uri if it's provided\n", + "if session_uri:\n", + " app_args[\"session_service_uri\"] = session_uri\n", + "else:\n", + " logger.log_text(\n", + " \"SESSION_SERVICE_URI not provided. Using in-memory session service instead. \"\n", + " \"All sessions will be lost when the server restarts.\",\n", + " severity=\"WARNING\",\n", + " )\n", + "\n", + "# Create FastAPI app with appropriate arguments\n", + "app: FastAPI = get_fast_api_app(**app_args)\n", + "\n", + "app.title = \"adk_cloudrun\"\n", + "app.description = \"ADK Agent CloudRun\"\n", + "\n", + "if __name__ == \"__main__\":\n", + " # Use the PORT environment variable provided by Cloud Run, defaulting to 8080\n", + " uvicorn.run(app, host=\"0.0.0.0\", port=int(os.environ.get(\"PORT\", 8080)))\n" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "85b5be928e90fff8", + "metadata": {}, + "source": [ + "#### Add all the necessary Python packages to the requirements.txt file:" + ] + }, + { + "cell_type": "code", + "id": "cd9ca7a1-667e-4fbb-a64e-35b1c2d62002", + "metadata": { + "tags": [] + }, + "source": [ + "%%writefile \"./adk_agents/requirements.txt\"\n", + "google-adk==1.15.1\n", + "google-cloud-aiplatform[adk,agent-engines]==1.121.0\n", + "python-dotenv>=1.0.1\n", + "pg8000>=1.31.2" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "60dbce64-b968-47f6-bc19-8081274734d9", + "metadata": { + "tags": [] + }, + "source": [ + "#### Define the container image:" + ] + }, + { + "cell_type": "code", + "id": "548aa7d2-0316-4d1e-87f0-ea90e358b94c", + "metadata": { + "tags": [] + }, + "source": [ + "%%writefile \"./adk_agents/Dockerfile\"\n", + "FROM python:3.13-slim\n", + "WORKDIR /app\n", + "\n", + "COPY requirements.txt .\n", + "RUN pip install --no-cache-dir -r requirements.txt\n", + "\n", + "RUN adduser --disabled-password --gecos \"\" myuser && \\\n", + " chown -R myuser:myuser /app\n", + "\n", + "COPY . .\n", + "\n", + "USER myuser\n", + "\n", + "ENV PATH=\"/home/myuser/.local/bin:$PATH\"\n", + "\n", + "CMD [\"sh\", \"-c\", \"uvicorn main:app --host 0.0.0.0 --port $PORT\"]" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "e94cdf37-cba1-4633-b2da-3c4fd7dcca51", + "metadata": {}, + "source": [ + "### Create a Cloud SQL instance for the agent sessions service.\n", + "We need a database to be utilized by the ADK agent for sessions. Create a PostgreSQL database on Cloud SQL" + ] + }, + { + "cell_type": "code", + "id": "512c0e45-6ec3-44fe-b9bc-b4e9546b0f99", + "metadata": { + "tags": [] + }, + "source": [ + "%%bash\n", + "gcloud sql instances create adk-demo-session-service \\\n", + " --database-version=POSTGRES_17 \\\n", + " --tier=db-g1-small \\\n", + " --region=us-central1 \\\n", + " --edition=ENTERPRISE \\\n", + " --root-password=adk-agent-demo" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "0a937bec-352d-405a-bc4d-02f59ceb552f", + "metadata": { + "tags": [] + }, + "source": [ + "#### Check your CloudSQL instance in the Cloud Console\n", + "Once created, you can view your instance in the Cloud Console [here](https://console.cloud.google.com/sql/instances/ds-agent-session-service/overview).\n" + ] + }, + { + "cell_type": "markdown", + "id": "8e028402-57c6-4f54-a92d-393f0fffc5ac", + "metadata": { + "tags": [] + }, + "source": [ + "### Deploy the agent to Cloud Run with CloudSQL session storage\n", + "Now we are ready to re-deploy the ADK Agent to Cloud Run!" + ] + }, + { + "cell_type": "markdown", + "id": "98ecfc5a-447c-4709-8a17-3936845afa99", + "metadata": {}, + "source": [ + "#### Create SQL connection string to store agent sessions:" + ] + }, + { + "cell_type": "code", + "id": "b0aa4f7d-f0f7-4eea-8908-b7773252eda9", + "metadata": { + "tags": [] + }, + "source": [ + "SESSION_SERVICE_URI = f\"postgresql+pg8000://postgres:adk-agent-demo@postgres/?unix_sock=/cloudsql/{PROJECT}:{LOCATION}:adk-demo-session-service/.s.PGSQL.5432\"\n", + "print(SESSION_SERVICE_URI)\n", + "os.environ[\"SESSION_SERVICE_URI\"] = SESSION_SERVICE_URI\n", + "ADK_CUSTOM_IMAGE_APP_NAME = \"adk-custom-image-demo-agent\"\n", + "os.environ[\"ADK_CUSTOM_IMAGE_APP_NAME\"] = ADK_CUSTOM_IMAGE_APP_NAME" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "e7a26902-5add-479e-a8dc-c2ec4e916237", + "metadata": { + "tags": [] + }, + "source": [ + "#### Deploy ADK Agent to CloudRun service:" + ] + }, + { + "cell_type": "code", + "id": "b088b83a-5d74-4f16-9959-cb942a1e06f6", + "metadata": { + "tags": [] + }, + "source": [ + "%%bash\n", + "cd adk_agents\n", + "gcloud run deploy $ADK_CUSTOM_IMAGE_APP_NAME \\\n", + " --source . \\\n", + " --port 8080 \\\n", + " --memory 2G \\\n", + " --project=$GOOGLE_CLOUD_PROJECT \\\n", + " --region=$GOOGLE_CLOUD_LOCATION \\\n", + " --allow-unauthenticated \\\n", + " --add-cloudsql-instances $GOOGLE_CLOUD_PROJECT:us-central1:adk-demo-session-service \\\n", + " --update-env-vars SERVE_WEB_INTERFACE=True,SESSION_SERVICE_URI=$SESSION_SERVICE_URI,GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "d5622772fe2a8a4d", + "metadata": {}, + "source": [ + "### Connect to Cloud Run app via Cloud Shell\n", + "\n", + "\n", + "You have a lot of flexibility when it comes to configuring access to your Cloud Run service. You can even [make it publicly accessible](https://cloud.google.com/run/docs/authenticating/public) if you want to.\n", + "\n", + "Connect to your Cloud Run app securely from Cloud Shell using a proxy:" + ] + }, + { + "cell_type": "code", + "id": "876bd044a4cbe275", + "metadata": {}, + "source": [ + "print(\n", + " f\"gcloud run services proxy {ADK_CUSTOM_IMAGE_APP_NAME} --project {PROJECT} --region {LOCATION}\"\n", + ")" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "id": "810c0e8f-13fd-430b-b9bd-603ea36ef44a", + "metadata": {}, + "source": [ + "#### Test a deployed agent!" + ] + }, + { + "cell_type": "markdown", + "id": "31650488-1e42-465e-8eab-05d3f092b5fb", + "metadata": {}, + "source": [ + "### Clean up\n", + "After you have finished, it is a good practice to clean up your cloud resources. \n", + "You can delete the deployed Agent Engine instance by using the next code:" + ] + }, + { + "cell_type": "markdown", + "id": "562b6138-4d26-40ca-a306-3470b1136367", + "metadata": {}, + "source": [ + "Copyright 2025 Google LLC\n", + "\n", + "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n", + "\n", + "https://www.apache.org/licenses/LICENSE-2.0\n", + "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." + ] + } + ], + "metadata": { + "environment": { + "kernel": "conda-env-adk_kernel-adk_kernel", + "name": "workbench-notebooks.m137", + "type": "gcloud", + "uri": "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/workbench-notebooks:m137" + }, + "kernelspec": { + "display_name": "ADK kernel (Local)", + "language": "python", + "name": "conda-env-adk_kernel-adk_kernel" + }, + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}