Files
2026-07-13 13:30:30 +08:00

609 lines
24 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"id": "26882ecf"
},
"outputs": [],
"source": [
"# Copyright 2026 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": "d0204763"
},
"source": [
"# Intro to Skill Registry\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/agents/skill-registry/intro_skill_registry.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%2Fagents%2Fskill-registry%2Fintro_skill_registry.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/agents/skill-registry/intro_skill_registry.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/agents/skill-registry/intro_skill_registry.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%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fblob%2Fmain%2Fagents%2Fskill-registry%2Fintro_skill_registry.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%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fblob%2Fmain%2Fagents%2Fskill-registry%2Fintro_skill_registry.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%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fblob%2Fmain%2Fagents%2Fskill-registry%2Fintro_skill_registry.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%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fblob%2Fmain%2Fagents%2Fskill-registry%2Fintro_skill_registry.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%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fblob%2Fmain%2Fagents%2Fskill-registry%2Fintro_skill_registry.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": "bhqCzRsBc_HO"
},
"source": [
"| Authors |\n",
"| --- |\n",
"| [Shirong Bai](https://github.com/srbai) |\n",
"| [Deep Dubey](https://github.com/deepdubey-git) |\n",
"| [Eric Dong](https://github.com/gericdong) |"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "c7366c8a"
},
"source": [
"## Overview\n",
"\n",
"The **Skill Registry** is a secure, private repository for storing, indexing, and dynamically discovering agent skills on the **Gemini Enterprise Agent Platform**.\n",
"\n",
"A **skill** extends an agent's capabilities by providing precise system instructions, domain-specific documentation, and local or remote tools. By indexing skills in the Registry, agents can perform semantic search at runtime to dynamically discover and load the exact capabilities needed to solve a user's request, optimizing context window usage and enforcing robust access control.\n",
"\n",
"### Objectives\n",
"\n",
"In this notebook, you will learn how to:\n",
"1. **Define a Custom Skill**: Create a local skill package structure with instructions and metadata.\n",
"2. **Upload and Register a Skill**: Programmatically register custom skills in the Skill Registry.\n",
"3. **Ingest Skills from GitHub**: Batch-import ready-to-use agent skills directly from a GitHub repository.\n",
"4. **Perform Semantic Discovery**: Retrieve and search relevant skills dynamically using semantic query matching.\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ffc9be79"
},
"source": [
"## Getting Started"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "59aa4204"
},
"source": [
"### Install libraries\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "2deb0b1c"
},
"outputs": [],
"source": [
"%pip install --upgrade --quiet \"google-cloud-aiplatform==1.152.0\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "i_JMBgbIuRJa"
},
"source": [
"⚠️ **Note**: Ignore pip dependency errors."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ca6812e7"
},
"source": [
"### Import libraries\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fc4d4ff9"
},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import sys\n",
"import shutil\n",
"import tempfile\n",
"import urllib.request\n",
"import yaml\n",
"import zipfile\n",
"import vertexai\n",
"\n",
"from datetime import datetime"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b2896b77"
},
"source": [
"### Authenticate your notebook environment\n",
"\n",
"If you are running this notebook in **Google Colab**, execute the cell below to authenticate."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "b3871f39"
},
"outputs": [],
"source": [
"if \"google.colab\" in sys.modules:\n",
" from google.colab import auth\n",
"\n",
" auth.authenticate_user()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8501ea4a"
},
"source": [
"### Autenticate your Google Cloud project\n",
"\n",
"You can use a Google Cloud Project or an API Key for authentication. This tutorial uses a Google Cloud Project.\n",
"\n",
"- [Enable the Agent Platform API](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "408d8e8b"
},
"outputs": [],
"source": [
"# fmt: off\n",
"PROJECT_ID = \"[your-project-id]\" # @param {type: \"string\", placeholder: \"[your-project-id]\", isTemplate: true}\n",
"# fmt: on\n",
"if not PROJECT_ID or PROJECT_ID == \"[your-project-id]\":\n",
" PROJECT_ID = str(os.getenv(\"GOOGLE_CLOUD_PROJECT\"))\n",
"\n",
"LOCATION = \"us-central1\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZeOjtnIHunUa"
},
"source": [
"### Initialize the Agent Platform client\n",
"\n",
"Initialize the SDK and instantiate the central platform client. This client is the entry point for managing custom resources (such as agents and skills) on the **Gemini Enterprise Agent Platform**."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "d04toxIHoOVX"
},
"outputs": [],
"source": [
"vertexai.init(project=PROJECT_ID, location=LOCATION)\n",
"\n",
"client = vertexai.Client(project=PROJECT_ID, location=LOCATION)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "11H8hKKNTAdn"
},
"source": [
"## Define and Register a Custom Skill\n",
"\n",
"To register a skill in the Skill Registry, you need a **skill package**. At its simplest, a skill package is a local directory containing a mandatory **`SKILL.md`** file.\n",
"\n",
"The `SKILL.md` file follows a specific layout:\n",
"1. **YAML Frontmatter**: A metadata block at the top specifying:\n",
" - `name`: A unique identifier matching the skill package name.\n",
" - `description`: A capability statement (starting in third-person) explaining what the skill does.\n",
"2. **Instructions**: The core markdown instructions that guide the agent's behavior when executing the skill.\n",
"\n",
"### Create a sample skill package\n",
"\n",
"The following cell programmatically creates a temporary directory (`/tmp/sample_math_skill`) containing a sample `SKILL.md` file for a simple math helper."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "4s0CiHIHuuQw"
},
"outputs": [],
"source": [
"# Create a temporary directory to simulate a local skill package\n",
"local_skill_dir = \"/tmp/sample_math_skill\"\n",
"os.makedirs(local_skill_dir, exist_ok=True)\n",
"\n",
"# Create the SKILL.md file which defines the skill\n",
"skill_md_content = \"\"\"---\n",
"name: sample-math-skill\n",
"description: A sample skill that helps with simple math addition. Use this skill when you need to add two numbers.\n",
"---\n",
"# Test Math Skill\n",
"This skill provides instructions on how to add two numbers.\n",
"\n",
"## Instructions\n",
"To add two numbers, sum them up. For example, 2 + 2 = 4.\n",
"\"\"\"\n",
"\n",
"with open(os.path.join(local_skill_dir, \"SKILL.md\"), \"w\") as f:\n",
" f.write(skill_md_content)\n",
"\n",
"print(f\"Created sample skill directory at: {local_skill_dir}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "bOZUxQQbvccL"
},
"source": [
"### Upload and register the skill package\n",
"\n",
"Now, upload and register your custom skill in the **Skill Registry** using `client.skills.create`.\n",
"\n",
"* The SDK will automatically package, compress, and upload the local skill folder specified in `\"local_path\"`.\n",
"* By default, the operation blocks synchronously (`wait_for_completion=True`) until the backend has successfully provisioned and indexed the skill."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "lQr6m-vGuxiN"
},
"outputs": [],
"source": [
"ts = datetime.now().strftime(\"%Y%m%d-%H%m%S\")\n",
"user_skill_id = f\"math-skill-{ts}\"\n",
"\n",
"try:\n",
" # Call create with local_path in config\n",
" # By default, wait_for_completion is True, so this will block until the skill is created\n",
" skill = client.skills.create(\n",
" display_name=\"Sample math skill\",\n",
" description=\"This skill provides functions to perform math calculations\",\n",
" config={\n",
" \"skill_id\": user_skill_id,\n",
" \"local_path\": \"/tmp/sample_math_skill\"\n",
" }\n",
" )\n",
" print(f\"SUCCESS: Skill created successfully!\")\n",
" print(f\"Skill Name: {skill.name}\")\n",
" print(f\"Skill State: {skill.state}\")\n",
" print(f\"Skill Display Name: {skill.display_name}\")\n",
" print(f\"Skill Description: {skill.description}\")\n",
"except Exception as e:\n",
" print(f\"FAILED: Skill creation failed: {e}\")\n",
" skill = None"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "KW9VO7tZTYFk"
},
"source": [
"## Batch Ingestion of Skills from a GitHub Repository\n",
"\n",
"In real-world production environments, organization-wide agent skills are often stored and managed in a central Git repository.\n",
"\n",
"This section demonstrates how to build an automated ingestion pipeline to fetch, parse, and batch-register skills from a remote repository.\n",
"\n",
"We will use the Google-managed **[google/skills](https://github.com/google/skills)** repository as our input. This repository contains ready-to-use agent skills, such as:\n",
"* **`bigquery-basics`**: Fundamental tools and instructions for querying Google Cloud BigQuery datasets.\n",
"* **`cloud-run-basics`**: Key operations and deployment guidelines for managing Google Cloud Run services.\n",
"\n",
"Once you run the pipeline with this repository as input, all contained skill packages will be programmatically ingested and registered inside your private **Skill Registry**, making them instantly discoverable.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "fsttmzOTwFXC"
},
"outputs": [],
"source": [
"GITHUB_REPO_URL = \"https://github.com/google/skills\" # @param {type:\"string\"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "xp12opJ9zPPw"
},
"source": [
"### Define pipeline helper functions\n",
"\n",
"Define the necessary Python helper functions to automate the ingestion workflow. This includes downloading the zip archive, extracting the files, scanning the directory tree, parsing frontmatter schemas, and registering each skill."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "meDWL38Vuz9r"
},
"outputs": [],
"source": [
"class Skill:\n",
" def __init__(self, name, description, local_skill_dir):\n",
" self.name = name\n",
" self.description = description\n",
" self.local_skill_dir = local_skill_dir\n",
"\n",
" def __str__(self):\n",
" return f\"Skill(name={self.name}, description={self.description}, local_skill_dir={self.local_skill_dir})\"\n",
"\n",
"def parse_skill_md(filepath: str) -> tuple[str, str]:\n",
" \"\"\"Parses SKILL.md file to get the skill name and description.\"\"\"\n",
" name = \"Untitled Skill\"\n",
" description = \"No description provided.\"\n",
" try:\n",
" with open(filepath, \"r\", encoding=\"utf-8\") as f:\n",
" content = f.read()\n",
" if content.startswith(\"---\"):\n",
" end_idx = content.find(\"---\", 3)\n",
" if end_idx != -1:\n",
" yaml_part = content[3:end_idx]\n",
" try:\n",
" data = yaml.safe_load(yaml_part)\n",
" if data:\n",
" name = data.get(\"name\", name)\n",
" description = data.get(\"description\", description)\n",
" except yaml.YAMLError as yaml_e:\n",
" print(f\"YAML parsing warning (using fallback): {yaml_e}\")\n",
" match_name = re.search(r\"^name:\\s*(.+)$\", yaml_part, re.M)\n",
" if match_name:\n",
" name = match_name.group(1).strip()\n",
" match_desc = re.search(\n",
" r\"^description:\\s*(?:>-\\s*)?(.+)$\", yaml_part, re.M\n",
" )\n",
" if match_desc:\n",
" description = match_desc.group(1).strip()\n",
" except IOError as e:\n",
" print(f\"Failed to parse {filepath}: {e}\")\n",
" return name, description\n",
"\n",
"def get_all_skills_from_dir(repo_dir: str) -> list[Skill]:\n",
" \"\"\"Returns a list of Skill objects from the given directory.\"\"\"\n",
" skills = []\n",
" skills_found = 0\n",
" seen_skills = set()\n",
" for root, dirs, files in os.walk(repo_dir):\n",
" lower_files = {f.lower(): f for f in files}\n",
" if \"skill.md\" in lower_files:\n",
" # Prune subdirectories to avoid deeper recursion in this skill folder.\n",
" dirs[:] = []\n",
"\n",
" skill_md_filename = lower_files[\"skill.md\"]\n",
" filepath = os.path.join(root, skill_md_filename)\n",
"\n",
" name, description = parse_skill_md(filepath)\n",
"\n",
" # De-dupe based on skill name\n",
" if name in seen_skills:\n",
" print(f\"Skipping duplicate skill name: {name}\")\n",
" continue\n",
" seen_skills.add(name)\n",
"\n",
" skills.append(Skill(name, description, root))\n",
" skills_found += 1\n",
" print(f\"Found {skills_found} skills.\")\n",
" return skills\n",
"\n",
"def download_github_repo(repo_url, output_dir):\n",
" os.makedirs(output_dir, exist_ok=True)\n",
" branch = \"master\"\n",
"\n",
" zip_url = f\"{GITHUB_REPO_URL}/archive/refs/heads/{branch}.zip\"\n",
" print(f\"Attempting to download repository zip via HTTP: {zip_url}...\")\n",
"\n",
" with tempfile.NamedTemporaryFile(suffix=\".zip\", delete=False) as temp_zip_file:\n",
" zip_path = temp_zip_file.name\n",
"\n",
" request = urllib.request.Request(zip_url)\n",
" with urllib.request.urlopen(request, timeout=30) as response:\n",
" with open(zip_path, \"wb\") as out_file:\n",
" shutil.copyfileobj(response, out_file)\n",
" with zipfile.ZipFile(zip_path, \"r\") as zip_ref:\n",
" zip_ref.extractall(output_dir)\n",
" print(\n",
" f\"Repository extracted successfully from '{branch}' branch zip\"\n",
" \" archive.\"\n",
" )\n",
" os.remove(zip_path)\n",
" print(f\"Deleted temporary zip file: {zip_path}\")\n",
"\n",
"def create_skill(skill):\n",
" name = skill.name\n",
" description = skill.description\n",
" skill_dir = skill.local_skill_dir\n",
" print(f\"Creating skill: {skill}\")\n",
" timestamp = datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n",
" skill = client.skills.create(\n",
" display_name=name,\n",
" description=description,\n",
" config={\n",
" \"local_path\": skill_dir,\n",
" \"skill_id\": f\"{name}-{timestamp}\"\n",
" }\n",
" )\n",
" print(f\"Created skill: {name}\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "hnXuKGSwzW8_"
},
"source": [
"### Run the batch ingestion pipeline\n",
"\n",
"Specify your target repository URL, trigger the download, and execute the bulk skill ingestion. Each discovered skill will be individually registered with a unique timestamped identifier."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "VUMCt_Upv5f2"
},
"outputs": [],
"source": [
"output_dir = tempfile.mkdtemp()\n",
"download_github_repo(GITHUB_REPO_URL, output_dir)\n",
"\n",
"skills = get_all_skills_from_dir(output_dir)\n",
"for skill in skills:\n",
" create_skill(skill)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7MUiHGopzlyi"
},
"source": [
"## Semantic Skill Discovery and Retrieval\n",
"\n",
"The Skill Registry has built-in vector search capabilities. When skills are registered, their metadata descriptions are automatically vectorized and indexed.\n",
"\n",
"This allows you to perform **semantic search** using natural language queries (e.g., searching for \"firebase\" or \"database helpers\") to locate the most relevant skills, even if there are no exact keyword matches.\n",
"\n",
"Agents use this exact retrieval mechanism at runtime to dynamically discover and load matching skills based on the user's intent."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "exBvtU0AyHcp"
},
"outputs": [],
"source": [
"print(f\"\\n--- Search Relevant Skills ---\")\n",
"query = \"firebase\"\n",
"print(f\"Searching for skills matching query: '{query}'...\")\n",
"try:\n",
" # Call retrieve to perform semantic search\n",
" response = client.skills.retrieve(\n",
" query=query,\n",
" config={\"top_k\": 2}\n",
" )\n",
" print(f\"SUCCESS: Skills retrieved successfully!\")\n",
" print(f\"Found {len(response.retrieved_skills)} matching skills.\")\n",
" for i, retrieved in enumerate(response.retrieved_skills):\n",
" print(f\" [{i+1}] Skill Name: {retrieved.skill_name}\")\n",
" print(f\" [{i+1}] Description: {retrieved.description}\")\n",
"except Exception as e:\n",
" print(f\"FAILED: Retrieve skills failed: {e}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Eyxfo3uqnWre"
},
"outputs": [],
"source": []
}
],
"metadata": {
"colab": {
"provenance": [],
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}