842 lines
32 KiB
Plaintext
842 lines
32 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "yFeds7eiwI6x"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# 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",
|
|
"# https://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."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "4ircZQgHwRdy"
|
|
},
|
|
"source": [
|
|
"# Intro to Computer Use with Gemini\n",
|
|
"\n",
|
|
"<table align=\"left\">\n",
|
|
" <td style=\"text-align: center\">\n",
|
|
" <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\">\n",
|
|
" <img width=\"32px\" src=\"https://www.gstatic.com/pantheon/images/bigquery/welcome_page/colab-logo.svg\" alt=\"Google Colaboratory logo\"><br> Open in Colab\n",
|
|
" </a>\n",
|
|
" </td>\n",
|
|
" <td style=\"text-align: center\">\n",
|
|
" <a href=\"https://console.cloud.google.com/agent-platform/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fmain%2Fgemini%2Fcomputer-use%2Fintro_computer_use.ipynb\">\n",
|
|
" <img width=\"32px\" src=\"https://lh3.googleusercontent.com/JmcxdQi-qOpctIvWKgPtrzZdJJK-J3sWE1RsfjZNwshCFgE_9fULcNpuXYTilIR2hjwN\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
|
|
" </a>\n",
|
|
" </td>\n",
|
|
" <td style=\"text-align: center\">\n",
|
|
" <a href=\"https://console.cloud.google.com/agent-platform/workbench/instances?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/generative-ai/main/gemini/computer-use/intro_computer_use.ipynb\">\n",
|
|
" <img width=\"32px\" src=\"https://storage.googleapis.com/github-repo/workbench-icon.svg\" alt=\"Workbench logo\"><br> Open in Workbench\n",
|
|
" </a>\n",
|
|
" </td>\n",
|
|
" <td style=\"text-align: center\">\n",
|
|
" <a href=\"https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\">\n",
|
|
" <img width=\"32px\" src=\"https://raw.githubusercontent.com/primer/octicons/refs/heads/main/icons/mark-github-24.svg\" alt=\"GitHub logo\"><br> View on GitHub\n",
|
|
" </a>\n",
|
|
" </td>\n",
|
|
"</table>\n",
|
|
"\n",
|
|
"<div style=\"clear: both;\"></div>\n",
|
|
"\n",
|
|
"<p>\n",
|
|
"<b>Share to:</b>\n",
|
|
"\n",
|
|
"<a href=\"https://www.linkedin.com/sharing/share-offsite/?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\" target=\"_blank\">\n",
|
|
" <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/8/81/LinkedIn_icon.svg\" alt=\"LinkedIn logo\">\n",
|
|
"</a>\n",
|
|
"\n",
|
|
"<a href=\"https://bsky.app/intent/compose?text=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\" target=\"_blank\">\n",
|
|
" <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/7/7a/Bluesky_Logo.svg\" alt=\"Bluesky logo\">\n",
|
|
"</a>\n",
|
|
"\n",
|
|
"<a href=\"https://twitter.com/intent/tweet?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\" target=\"_blank\">\n",
|
|
" <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/5a/X_icon_2.svg\" alt=\"X logo\">\n",
|
|
"</a>\n",
|
|
"\n",
|
|
"<a href=\"https://reddit.com/submit?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\" target=\"_blank\">\n",
|
|
" <img width=\"20px\" src=\"https://redditinc.com/hubfs/Reddit%20Inc/Brand/Reddit_Logo.png\" alt=\"Reddit logo\">\n",
|
|
"</a>\n",
|
|
"\n",
|
|
"<a href=\"https://www.facebook.com/sharer/sharer.php?u=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/computer-use/intro_computer_use.ipynb\" target=\"_blank\">\n",
|
|
" <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg\" alt=\"Facebook logo\">\n",
|
|
"</a>\n",
|
|
"</p>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "MJDxNAGItD51"
|
|
},
|
|
"source": [
|
|
"| Authors |\n",
|
|
"| --- |\n",
|
|
"| [Eric Dong](https://github.com/gericdong) |\n",
|
|
"| [Holt Skinner](https://github.com/holtskinner) |"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "896Uhs2Ww6_E"
|
|
},
|
|
"source": [
|
|
"## Overview\n",
|
|
"\n",
|
|
"The **Gemini Computer Use** tool lets you create agents that can automate tasks on a computer. It works by \"seeing\" the screen with screenshots and then \"acting\" with mouse clicks and keyboard inputs.\n",
|
|
"\n",
|
|
"This is useful for tasks like:\n",
|
|
"\n",
|
|
"- Automatically filling out forms on websites.\n",
|
|
"- Testing web applications.\n",
|
|
"- Researching information, like comparing prices, across different sites.\n",
|
|
"\n",
|
|
"Learn more about [computer use](https://cloud.google.com/vertex-ai/generative-ai/docs/computer-use).\n",
|
|
"\n",
|
|
"\n",
|
|
"## Objective\n",
|
|
"\n",
|
|
"In this tutorial, you will build a simple web automation agent using the Gemini Computer Use tool. By the end, you will understand the complete workflow: from sending an initial prompt with a screenshot to executing browser actions and looping until a task is complete."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "gPiTOAHURvTM"
|
|
},
|
|
"source": [
|
|
"## Getting Started"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "CHRZUpfWSEpp"
|
|
},
|
|
"source": [
|
|
"### Install the Gen AI SDK and required libraries"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "sG3_LKsWSD3A"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install --upgrade --quiet google-genai playwright"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "msiBxrk0ATzB"
|
|
},
|
|
"source": [
|
|
"> ⚠️ Note: You can ignore the pip's dependency errors."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "WiZkhIF41qhY"
|
|
},
|
|
"source": [
|
|
"### Set up Playwright\n",
|
|
"\n",
|
|
"Playwright is a tool for browser automation. It enables browser control over web browsers like Chromium, Firefox, and WebKit.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "zL7fxJtq1cWT"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%capture\n",
|
|
"\n",
|
|
"# Installs Playwright and browsers\n",
|
|
"!playwright install\n",
|
|
"\n",
|
|
"# Additional command, mandatory for Linux only\n",
|
|
"!playwright install-deps"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "rK3jJDR5lfiT"
|
|
},
|
|
"source": [
|
|
"### Import libraries\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "T6DyDoNclVEn"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import logging\n",
|
|
"import os\n",
|
|
"import sys\n",
|
|
"import time\n",
|
|
"from types import SimpleNamespace\n",
|
|
"\n",
|
|
"from google import genai\n",
|
|
"from google.genai.types import (\n",
|
|
" ComputerUse,\n",
|
|
" Content,\n",
|
|
" Environment,\n",
|
|
" FunctionCall,\n",
|
|
" FunctionResponse,\n",
|
|
" FunctionResponseBlob,\n",
|
|
" GenerateContentConfig,\n",
|
|
" Part,\n",
|
|
" ThinkingConfig,\n",
|
|
" Tool,\n",
|
|
")\n",
|
|
"from playwright.async_api import async_playwright\n",
|
|
"\n",
|
|
"logging.getLogger(\"google_genai._common\").setLevel(logging.ERROR)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "HlMVjiAWSMNX"
|
|
},
|
|
"source": [
|
|
"### Authenticate your notebook environment\n",
|
|
"\n",
|
|
"If you are running this notebook on Google Colab, run the cell below to authenticate your environment."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "12fnq4V0SNV3"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"if \"google.colab\" in sys.modules:\n",
|
|
" from google.colab import auth\n",
|
|
"\n",
|
|
" auth.authenticate_user()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "be18ac9c5ec8"
|
|
},
|
|
"source": [
|
|
"### Set your project information\n",
|
|
"\n",
|
|
"Update the following variables with your Google Cloud project details, and connect to the Gen AI service on Vertex AI."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "6wXh1aH7BlPl"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# fmt: off\n",
|
|
"PROJECT_ID = \"[your-project-id]\" # @param {type: \"string\", placeholder: \"[your-project-id]\", isTemplate: true}\n",
|
|
"# fmt: on\n",
|
|
"LOCATION = \"global\" # @param {type: \"string\"}\n",
|
|
"\n",
|
|
"if not PROJECT_ID or PROJECT_ID == \"[your-project-id]\":\n",
|
|
" PROJECT_ID = str(os.environ.get(\"GOOGLE_CLOUD_PROJECT\"))\n",
|
|
"\n",
|
|
"# Connect to the Gen AI service on Vertex AI\n",
|
|
"client = genai.Client(enterprise=True, project=PROJECT_ID, location=LOCATION)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "n4yRkFg6BBu4"
|
|
},
|
|
"source": [
|
|
"### Supported Models\n",
|
|
"\n",
|
|
"This tutorial uses the `gemini-3.5-flash` model."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "-coEslfWPrxo"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# fmt: off\n",
|
|
"MODEL_ID = \"gemini-3.5-flash\" # @param [\"gemini-3.5-flash\", \"gemini-2.5-computer-use-preview-10-2025\"]\n",
|
|
"# fmt: on"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "KqjCltg20IgR"
|
|
},
|
|
"source": [
|
|
"## Computer Use: Agent Loop\n",
|
|
"\n",
|
|
"To build a browser control agent, you implement an \"agent loop\" that continuously cycles through four key steps. This process allows the agent to perform a sequence of actions to achieve a goal.\n",
|
|
"\n",
|
|
"1. **Send Request to the Model**. Your app sends the goal (e.g., \"Find me a flight\") and a current screenshot of the screen to the model.\n",
|
|
"\n",
|
|
"2. **Receive the Model Response**. The model analyzes the screen and sends back a suggested action, like navigate to a URL. It may also include a safety warning for risky actions.\n",
|
|
"\n",
|
|
"3. **Execute the Received Action**. Your code runs the suggested action. If there's a safety warning, your code must ask the user for confirmation before proceeding.\n",
|
|
"\n",
|
|
"\n",
|
|
"4. **Capture the New Environment State**. After the action, your code takes a new screenshot. This new screenshot is sent back to the model in the next turn, starting the cycle over again."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "L85sHbJv9cpF"
|
|
},
|
|
"source": [
|
|
"## Prerequisites: Setting Up Your Environment\n",
|
|
"\n",
|
|
"Before you begin, you need to set up two key components:\n",
|
|
"\n",
|
|
"- **Secure Execution Environment**: For safety, you must run your Computer Use agent in a secure and controlled environment. Good options include a sandboxed virtual machine, a container, or a dedicated browser profile with limited permissions.\n",
|
|
"\n",
|
|
"- **Client-Side Action Handler**: You need to write client-side logic to execute the actions generated by the model (e.g., clicking a button) and capture screenshots.\n",
|
|
"\n",
|
|
"In this tutorial, we use Playwright to start a browser environment for demonstration purpose."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "vU5s-EBxCGiN"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Start the Playwright session\n",
|
|
"playwright = await async_playwright().start()\n",
|
|
"\n",
|
|
"# Launch the browser in headless mode, which is required for this environment\n",
|
|
"browser = await playwright.chromium.launch(headless=True)\n",
|
|
"\n",
|
|
"# Create a new page\n",
|
|
"page = await browser.new_page()\n",
|
|
"screen_width, screen_height = 1920, 1080\n",
|
|
"await page.set_viewport_size({\"width\": screen_width, \"height\": screen_height})\n",
|
|
"\n",
|
|
"print(\"Playwright session started.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "nNhzdiH3-str"
|
|
},
|
|
"source": [
|
|
"## A Single Turn: Step-by-Step Walkthrough\n",
|
|
"\n",
|
|
"Now, let's walk through the code for a single turn of the agent loop, from sending the first request to preparing for the next one.\n",
|
|
"\n",
|
|
"### **1. Send a Request to the Model**\n",
|
|
"First, you configure your API request. In the request, you add the Computer Use tool and send a prompt that includes the user's goal and an initial screenshot.\n",
|
|
"\n",
|
|
"You can also include optional parameters like `excluded_predefined_functions` to prevent the model from using certain actions."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "M8CdwkdPAC8e"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Configure Computer Use tool with browser environment\n",
|
|
"# Base configuration for the Computer Use tool\n",
|
|
"config_kwargs = {\n",
|
|
" \"tools\": [\n",
|
|
" Tool(\n",
|
|
" computer_use=ComputerUse(\n",
|
|
" environment=Environment.ENVIRONMENT_BROWSER,\n",
|
|
" # Optional: Exclude specific predefined functions\n",
|
|
" excluded_predefined_functions=[\"drag_and_drop\"],\n",
|
|
" )\n",
|
|
" )\n",
|
|
" ]\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Conditionally add thinking_config only for the Gemini 3 models\n",
|
|
"model_version = float(MODEL_ID.split(\"-\")[1])\n",
|
|
"if model_version >= 3:\n",
|
|
" config_kwargs[\"thinking_config\"] = ThinkingConfig(include_thoughts=True)\n",
|
|
"\n",
|
|
"config = GenerateContentConfig(**config_kwargs)\n",
|
|
"\n",
|
|
"# Create the content with user message and initial screenshot\n",
|
|
"screenshot = await page.screenshot()\n",
|
|
"\n",
|
|
"contents = [\n",
|
|
" Content(\n",
|
|
" role=\"user\",\n",
|
|
" parts=[\n",
|
|
" Part(\n",
|
|
" text=\"Find me a flight from SF to Hawaii on next Monday, coming back on next Friday. Start by navigating directly to flights.google.com\"\n",
|
|
" ),\n",
|
|
" # Optional: include a screenshot of the initial state\n",
|
|
" Part.from_bytes(\n",
|
|
" data=screenshot,\n",
|
|
" mime_type=\"image/png\",\n",
|
|
" ),\n",
|
|
" ],\n",
|
|
" )\n",
|
|
"]\n",
|
|
"\n",
|
|
"# Generate content with the configured settings\n",
|
|
"response = client.models.generate_content(\n",
|
|
" model=MODEL_ID,\n",
|
|
" contents=contents,\n",
|
|
" config=config,\n",
|
|
")\n",
|
|
"\n",
|
|
"print(response)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "M8ymc2GH_xxs"
|
|
},
|
|
"source": [
|
|
"### **2. Receive the Model Response**\n",
|
|
"The model responds with one or more `FunctionCalls` that represent the UI actions it wants to perform. Let's inspect the response from our first API call."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "aC4wcsEBJ9vt"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"response.function_calls"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "2vj9YwsxKvdg"
|
|
},
|
|
"source": [
|
|
"### **3. Execute the Received Actions**\n",
|
|
"\n",
|
|
"Next, our application's client-side code needs to parse the response and execute the requested actions using Playwright. We'll use the `execute_function_calls` helper function for this.\n",
|
|
"\n",
|
|
"The following example implements some most common UI actions. For a production use case, you would need to implement all supported actions unless you explicitly exclude them."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "tQqZqdTCf1YH"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"def normalize_x(x: int, screen_width: int) -> int:\n",
|
|
" \"\"\"Convert normalized x coordinate (0-1000) to actual pixel coordinate.\"\"\"\n",
|
|
" return int(x / 1000 * screen_width)\n",
|
|
"\n",
|
|
"\n",
|
|
"def normalize_y(y: int, screen_height: int) -> int:\n",
|
|
" \"\"\"Convert normalized y coordinate (0-1000) to actual pixel coordinate.\"\"\"\n",
|
|
" return int(y / 1000 * screen_height)\n",
|
|
"\n",
|
|
"\n",
|
|
"async def execute_function_calls(\n",
|
|
" response, page, screen_width, screen_height\n",
|
|
") -> list[tuple[str, str]]:\n",
|
|
" \"\"\"Extracts and executes function calls from the model response.\"\"\"\n",
|
|
" candidate = response.candidates[0]\n",
|
|
" function_calls = []\n",
|
|
" thoughts = []\n",
|
|
"\n",
|
|
" for part in candidate.content.parts:\n",
|
|
" if hasattr(part, \"function_call\") and part.function_call:\n",
|
|
" function_calls.append(part.function_call)\n",
|
|
" elif hasattr(part, \"text\") and part.text:\n",
|
|
" thoughts.append(part.text)\n",
|
|
"\n",
|
|
" if thoughts:\n",
|
|
" print(f\" Model Reasoning: {' '.join(thoughts)}\")\n",
|
|
"\n",
|
|
" if not function_calls:\n",
|
|
" return \"NO_ACTION\", []\n",
|
|
"\n",
|
|
" results = []\n",
|
|
" for function_call in function_calls:\n",
|
|
" result = None\n",
|
|
" print(f\"⚡ Executing Action: {function_call.name}\")\n",
|
|
" try:\n",
|
|
" if function_call.name == \"open_web_browser\":\n",
|
|
" result = \"success\"\n",
|
|
" elif function_call.name == \"navigate\":\n",
|
|
" await page.goto(function_call.args[\"url\"])\n",
|
|
" result = \"success\"\n",
|
|
" elif function_call.name == \"click_at\":\n",
|
|
" actual_x = normalize_x(function_call.args[\"x\"], screen_width)\n",
|
|
" actual_y = normalize_y(function_call.args[\"y\"], screen_height)\n",
|
|
" await page.mouse.click(actual_x, actual_y)\n",
|
|
" result = \"success\"\n",
|
|
" elif function_call.name == \"type_text_at\":\n",
|
|
" actual_x = normalize_x(function_call.args[\"x\"], screen_width)\n",
|
|
" actual_y = normalize_y(function_call.args[\"y\"], screen_height)\n",
|
|
" await page.mouse.click(actual_x, actual_y)\n",
|
|
" time.sleep(0.1)\n",
|
|
" await page.keyboard.type(function_call.args[\"text\"])\n",
|
|
" if function_call.args.get(\"press_enter\", False):\n",
|
|
" await page.keyboard.press(\"Enter\")\n",
|
|
" result = \"success\"\n",
|
|
" else:\n",
|
|
" result = \"unknown_function\"\n",
|
|
" except Exception as e:\n",
|
|
" print(f\"❗️ Error executing {function_call.name}: {e}\")\n",
|
|
" result = f\"error: {e!s}\"\n",
|
|
" results.append((function_call.name, result))\n",
|
|
" return \"CONTINUE\", results"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "_SP2x-IjMSzr"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"results = await execute_function_calls(response, page, screen_width, screen_height)\n",
|
|
"print(results)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "y287MbtSJ7bs"
|
|
},
|
|
"source": [
|
|
"Here is an example action for navigating a URL. In this case, we create a simple mock response object.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "AubneGmKA2bw"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"mock_response = SimpleNamespace(\n",
|
|
" candidates=[\n",
|
|
" SimpleNamespace(\n",
|
|
" content=SimpleNamespace(\n",
|
|
" parts=[\n",
|
|
" SimpleNamespace(\n",
|
|
" function_call=FunctionCall(\n",
|
|
" name=\"navigate\", args={\"url\": \"https://flights.google.com\"}\n",
|
|
" ),\n",
|
|
" )\n",
|
|
" ]\n",
|
|
" )\n",
|
|
" )\n",
|
|
" ]\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "OW9N3x6kC-kc"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(f\"Current page URL: {page.url}\")\n",
|
|
"\n",
|
|
"print(\"Calling execute_function_calls with a sample response\")\n",
|
|
"results = await execute_function_calls(mock_response, page, screen_width, screen_height)\n",
|
|
"print(f\"Results from execution:\\n{results}\\n\")\n",
|
|
"\n",
|
|
"print(f\"Navigated to: {page.url}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "dkndDAPXNLQx"
|
|
},
|
|
"source": [
|
|
"### **4. Capture the New State and Respond**\n",
|
|
"\n",
|
|
"Finally, after executing the actions, we capture a new screenshot and the current URL. This state information is then formatted as a `FunctionResponse` and added to our conversation history, making it ready for the next turn in the loop."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "8QcMbBE5NUHU"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"status, action_results_list = results\n",
|
|
"\n",
|
|
"function_response_parts = []\n",
|
|
"\n",
|
|
"for name, result in action_results_list:\n",
|
|
" # After each action, capture a new screenshot and the current URL\n",
|
|
" screenshot = await page.screenshot()\n",
|
|
" current_url = page.url\n",
|
|
"\n",
|
|
" # Create a FunctionResponse for each action that was executed\n",
|
|
" # This is required even if multiple actions were called in parallel\n",
|
|
" function_response_parts.append(\n",
|
|
" Part(\n",
|
|
" function_response=FunctionResponse(\n",
|
|
" name=name,\n",
|
|
" response={\"url\": current_url},\n",
|
|
" parts=[\n",
|
|
" Part(\n",
|
|
" inline_data=FunctionResponseBlob(\n",
|
|
" mime_type=\"image/png\", data=screenshot\n",
|
|
" )\n",
|
|
" )\n",
|
|
" ],\n",
|
|
" )\n",
|
|
" )\n",
|
|
" )\n",
|
|
"\n",
|
|
"# Package all the function responses into a single 'user' message\n",
|
|
"user_feedback_content = Content(role=\"user\", parts=function_response_parts)\n",
|
|
"\n",
|
|
"# Append this new message to your conversation history\n",
|
|
"contents.append(user_feedback_content)\n",
|
|
"\n",
|
|
"print(\"Step 4 Complete: New state captured and added to conversation history.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "YAZBbH3hD1y8"
|
|
},
|
|
"source": [
|
|
"The `contents` list is now ready for the next call to the model."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "O3LJGCkHD640"
|
|
},
|
|
"source": [
|
|
"#### Clean up by closing the browser and stopping the session\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "GKoItatcCqrV"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"await browser.close()\n",
|
|
"await playwright.stop()\n",
|
|
"\n",
|
|
"print(\"Browser closed and Playwright session stopped.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "sZR7UOfgOdji"
|
|
},
|
|
"source": [
|
|
"## Build an Agent Loop\n",
|
|
"\n",
|
|
"To enable multi-step interactions, combine the four steps from the How to implement Computer Use section into a loop. The loop must handle parallel function calls, and safety decisions. Remember to manage the conversation history correctly by appending both model responses and your function responses."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "m5mYV8CbZE05"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"async def agent_loop(initial_prompt, max_turns=20):\n",
|
|
" \"\"\"Main agent loop\"\"\"\n",
|
|
" playwright_loop = await async_playwright().start()\n",
|
|
" browser_loop = await playwright_loop.chromium.launch(headless=True)\n",
|
|
" page_loop = await browser_loop.new_page()\n",
|
|
" sw, sh = 1920, 1080\n",
|
|
" await page_loop.set_viewport_size({\"width\": sw, \"height\": sh})\n",
|
|
"\n",
|
|
" print(f\"Starting Agent Loop with prompt: '{initial_prompt}'\")\n",
|
|
"\n",
|
|
" screenshot = await page_loop.screenshot()\n",
|
|
" contents = [\n",
|
|
" Content(\n",
|
|
" role=\"user\",\n",
|
|
" parts=[\n",
|
|
" Part(text=initial_prompt),\n",
|
|
" Part.from_bytes(data=screenshot, mime_type=\"image/png\"),\n",
|
|
" ],\n",
|
|
" )\n",
|
|
" ]\n",
|
|
"\n",
|
|
" for turn in range(max_turns):\n",
|
|
" print(f\"\\n Turn {turn + 1}\")\n",
|
|
"\n",
|
|
" response = client.models.generate_content(\n",
|
|
" model=MODEL_ID, contents=contents, config=config\n",
|
|
" )\n",
|
|
"\n",
|
|
" # Handle cases where the model returns no candidates (e.g., due to safety filters)\n",
|
|
" if not response.candidates:\n",
|
|
" print(\"Model returned no candidates. This may be due to a safety filter.\")\n",
|
|
" print(\"Full Response:\", response)\n",
|
|
" print(\"Terminating loop.\")\n",
|
|
" break\n",
|
|
"\n",
|
|
" contents.append(response.candidates[0].content)\n",
|
|
"\n",
|
|
" function_calls = [\n",
|
|
" part.function_call\n",
|
|
" for part in response.candidates[0].content.parts\n",
|
|
" if hasattr(part, \"function_call\") and part.function_call\n",
|
|
" ]\n",
|
|
"\n",
|
|
" # Finish the agent loop if no function call in the response.\n",
|
|
" if not function_calls:\n",
|
|
" final_text = \"\".join(\n",
|
|
" part.text\n",
|
|
" for part in response.candidates[0].content.parts\n",
|
|
" if hasattr(part, \"text\") and part.text is not None\n",
|
|
" )\n",
|
|
" if final_text:\n",
|
|
" print(f\"Agent Finished: {final_text}\")\n",
|
|
" break\n",
|
|
"\n",
|
|
" status, execution_results = await execute_function_calls(\n",
|
|
" response, page_loop, sw, sh\n",
|
|
" )\n",
|
|
"\n",
|
|
" if status == \"NO_ACTION\":\n",
|
|
" continue\n",
|
|
"\n",
|
|
" function_response_parts = []\n",
|
|
" for name, result in execution_results:\n",
|
|
" screenshot = await page_loop.screenshot()\n",
|
|
" current_url = page_loop.url\n",
|
|
" function_response_parts.append(\n",
|
|
" Part(\n",
|
|
" function_response=FunctionResponse(\n",
|
|
" name=name,\n",
|
|
" response={\"url\": current_url},\n",
|
|
" parts=[\n",
|
|
" Part(\n",
|
|
" inline_data=FunctionResponseBlob(\n",
|
|
" mime_type=\"image/png\", data=screenshot\n",
|
|
" )\n",
|
|
" )\n",
|
|
" ],\n",
|
|
" )\n",
|
|
" )\n",
|
|
" )\n",
|
|
" contents.append(Content(role=\"user\", parts=function_response_parts))\n",
|
|
" print(f\"State captured. History now has {len(contents)} messages.\")\n",
|
|
"\n",
|
|
" print(\"\\n Agent loop finished. Closing browser.\")\n",
|
|
" await browser_loop.close()\n",
|
|
" await playwright_loop.stop()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "hX30dnPKnXYK"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"# RUN THE AGENT LOOP\n",
|
|
"prompt = \"Navigate to the Google Store and find the 'Pixel' category.\"\n",
|
|
"\n",
|
|
"await agent_loop(prompt)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "gj9KyrqG3n3s"
|
|
},
|
|
"source": [
|
|
"## Next Steps\n",
|
|
"\n",
|
|
"- Explore a [Computer Use web agent reference implementation](https://github.com/GoogleCloudPlatform/generative-ai/tree/main/gemini/computer-use/web-agent).\n",
|
|
"- Check out the [Computer Use documentation](https://ai.google.dev/gemini-api/docs/computer-use) for detailed guides, parameter references, and best practices.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"colab": {
|
|
"name": "intro_computer_use.ipynb",
|
|
"toc_visible": true
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"name": "python3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 0
|
|
}
|