[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M698YT1HRG6WR1V5GTKAB896":3,"skill-same:01M698YT1HRG6WR1V5GTKAB896":130},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M698YT1HRG6WR1V5GTKAB896","agent-platform-tuning",null,"Agent Platform Model Tuning. Use when you need to fine-tune open models or Gemini models using Agent Platform infrastructure. Don't use for model training outside Agent Platform, model deployment to endpoints (use `agent-platform-deploy`), or managing serving endpoints (use `agent-platform-endpoint-management`).","google\u002Fskills","google","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1342004?v=4","01M698VYEFDM0VJBZDYYJ2T83S","Google","01M698WR0H76ENHE587T9EJT89",24122,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"agent platform tuning","agent platform","platform","tuning","models","gemini","when","serving","for","agent-platform","you","tune","deploy","endpoints","infrastructure","use","management","model","open","model deployment","endpoint","deployment","training","2026-11-10 10:12:30",false,"---\nname: agent-platform-tuning\nmetadata:\n  version: \"1.0.2\"\n  category: AiAndMachineLearning\ndescription: >-\n  Agent Platform Model Tuning. Use when you need to fine-tune open models\n  or Gemini models using Agent Platform infrastructure. Don't use for model\n  training outside Agent Platform, model deployment to endpoints (use\n  `agent-platform-deploy`), or managing serving endpoints (use\n  `agent-platform-endpoint-management`).\n---\n\n# Agent Platform Model Tuning\n\n## Attribution\n\n**MUST READ**: Before executing any `gcloud`, REST API, or MCP command, you MUST\nread [Usage Attribution](references\u002Fattribution.md). For commands executed\ndirectly in the terminal (never in generated scripts, Terraform, or user code),\nprefix `gcloud` with `CLOUDSDK_METRICS_REQUEST_ATTRIBUTION` and append the\nattribution suffix to `User-Agent` headers:\n\n```text\nCLOUDSDK_METRICS_REQUEST_ATTRIBUTION=\"agent-skills\u002F1.0 (skill:agent-platform-tuning)\"\nUser-Agent: \u003Cexisting_user_agent> agent-skills\u002F1.0 (skill:agent-platform-tuning)\n```\n\n## Overview\n\nThis skill provides procedural knowledge for fine-tuning Large Language Models\n(both Open Models and Gemini Models) using Agent Platform's tuning service. It\ncovers the entire lifecycle from environment setup and data preparation to job\nconfiguration, monitoring, and deployment.\n\n## Workflow Decision Tree\n\n1.  **Project & Region Verification Check**: Has the user provided the Google\n    Cloud project and region?\n\n    -   **No** → **STOP tool execution immediately**. Do NOT run verification\n        commands (`gcloud services list`, `gcloud projects get-iam-policy`), do\n        NOT create resources, and do NOT begin dataset preparation. Prompt the\n        user to specify or confirm the project and region (e.g. \"Could you\n        please specify which Google Cloud project and region you would like to\n        use?\").\n        -   If the user's inquiry is solely to check or verify environment\n            readiness (APIs, IAM, service agents), ask ONLY for the project and\n            region. Do NOT ask for the model category.\n        -   If the user is requesting a tuning workflow and also omitted whether\n            they want to tune an Open Model or a Gemini Model, you may ask both\n            questions together.\n    -   **Yes** → Proceed.\n\n2.  **Model Category Identification**: Has the user explicitly stated whether\n    they want to tune an **Open Model** or a **Gemini Model**?\n\n    -   **No** →\n        -   **EXCEPTION for Environment Verification Inquiries:** If the user is\n            only asking to check or verify that the environment, APIs, IAM\n            permissions, or service agents are ready for tuning, do NOT ask for\n            the model category. Verify the environment once the project and\n            region are known and confirm readiness.\n        -   Otherwise, **STOP tool execution**. Ask the user if they want to\n            tune an Open Model or a Gemini Model. **General Setup and\n            Prerequisite Inquiries (e.g., \"What environment setup is\n            needed?\"):** If the user asks what environment setup, prerequisites,\n            APIs, or permissions are needed to start fine-tuning, and has not\n            yet chosen a model category:\n        *   Describe the setup requirements (APIs, IAM permissions\u002Fservice\n            agents, and Python SDKs).\n        *   Regarding Cloud Storage: state that an existing Cloud Storage bucket\n            is needed for datasets and artifacts (e.g.,\n            `gs:\u002F\u002F\u003Cexisting-bucket>`). **CRITICAL:** Do NOT instruct the user to\n            create a bucket, do NOT output a `gcloud storage buckets create`\n            command in setup instructions, and do NOT assume a non-existent\n            bucket exists (users may not have bucket creation permissions and\n            will provide their own existing bucket).\n        *   You MUST explicitly conclude your prose response with the direct\n            question: **\"Would you like to tune an Open Model or a Gemini\n            Model?\"** (do NOT bury this choice inside a bulleted \"Suggested Next\n            Steps\" list). Never provide setup instructions without explicitly\n            asking this question in prose. (Note: if they ask to actively check\n            or verify a project whose ID or region is missing, ask for the\n            project and region first without running tool calls).\n    -   If the user provides a specific tuning purpose, you should recommend\n        three models: one Open Model, one Gemini Model, and a third generally\n        recommended choice. Briefly list the pros and cons of each (e.g., Gemini\n        models might be more expensive, etc.). **CRITICAL:** You must read\n        `references\u002Fmodels.md` during this step and only recommend models\n        explicitly listed in that catalog. Never recommend uncataloged or\n        unsupported models like `google\u002Fgemma-2-9b-it`, `gemma-2`, or `Mistral`\n        — only recommend supported models such as Gemma 3\n        (`google\u002Fgemma3@gemma-3-12b-it`), Qwen 3 (`qwen\u002Fqwen3@qwen3-8b`), or\n        Llama 3.1 (`meta\u002Fllama3_1@llama-3.1-8b`). For Gemini models, ONLY\n        recommend `gemini-2.5-flash` (recommended for general\u002Fcoding\u002Fchat) or\n        `gemini-2.5-pro`. Never recommend `gemini-1.5-flash-002`,\n        `gemini-1.5-pro-002`, or `gemini-1.5-flash`, which are deprecated and\n        unsupported by the tuning service. If the user names a model that is not\n        in the catalog, follow the fallback rule in that catalog. Do not proceed\n        with model configuration until the category is confirmed.\n    -   **Distillation requests**: If the user asks to distill a model, names a\n        teacher model, or wants a smaller model to learn from a larger one, this\n        is an Open Model workflow using distillation. Read \"Distillation\" in\n        `references\u002Fmodels.md` and use only the teacher and student models\n        listed there.\n    -   **Yes** → Proceed.\n\n3.  **Environment Check**: Has the environment (Auth, APIs, IAM, Venv) been\n    initialized?\n\n    -   **No** → Go to [Phase 0: Environment & IAM Setup](#phase-0).\n    -   **Yes** → Proceed.\n\n4.  **Dataset Status**: Is the dataset ready in JSONL format, **is its structure\n    valid for tuning**, and is it uploaded to Google Cloud Storage?\n\n    ```\n    -   **No** → Go to [Phase 1: Dataset Preparation & Upload](#phase-1).\n    -   **Yes** → Proceed.\n    ```\n\n5.  **Column Selection Confirmation**: Have you presented the columns to the\n    user and confirmed the mapping?\n\n    -   **No** → **STOP**. You must show samples and get user confirmation on\n        column mapping as described in Phase 1.0 before proceeding.\n    -   **Yes** → Proceed.\n\n6.  **Configuration**: Has the user provided the target model and\n    hyperparameters, or explicitly agreed to your recommendations?\n\n    -   **No** → Go to\n        [Phase 2: Model Configuration & Recommendation](#phase-2).\n    -   **Yes** → Proceed.\n\n7.  **Job Status**: Has the tuning job been submitted?\n\n    ```\n    -   **No** → Go to\n        [Phase 3: Tuning Job Execution](#phase-3-tuning-job-execution).\n    -   **Yes** → Proceed.\n    ```\n\n8.  **Job Completion**: Is the tuning job complete?\n\n    ```\n    -   **No** → Go to [Phase 4: Monitoring](#phase-4-monitoring).\n    -   **Yes** → Proceed.\n    ```\n\n9.  **Deployment**: Has the tuned model been deployed (if required)?\n\n    ```\n    -   **No** → Go to [Phase 5: Model Deployment](#phase-5-model-deployment).\n    -   **Yes** → Task Complete.\n    ```\n\n## Phase 0: Environment & IAM Setup {#phase-0}\n\nEnsure the foundational environment is ready before proceeding.\n\n### 0.1 Authentication & Project Context\n\n-   Check if `gcloud` CLI is installed. If it is not installed, prompt the user\n    for permission to install it before proceeding. If it is installed, update\n    it:\n\n```bash\ngcloud components update --quiet > \u002Fdev\u002Fnull 2>&1\n```\n\n-   Verify `gcloud auth list`. If not authenticated, run `gcloud auth login`.\n-   **Project & Region Grounding**: Check if the user specified their GCP\n    project and region in their prompt. If the user's prompt omits either the\n    project or the region (e.g., in an environment verification or setup\n    request), you **MUST STOP tool execution immediately without running any\n    bash or gcloud commands** (do NOT call `gcloud config get project` or\n    `gcloud services list`). Ask the user to provide their project ID\u002Fnumber and\n    region.\n-   Once the project and region are provided or confirmed by the user, verify\n    that `gcloud` is authenticated and execute read-only checks to verify the\n    environment. When reporting environment readiness, your summary MUST\n    explicitly detail the status of all three categories:\n    1.  **Required APIs**: explicitly report that both\n        `aiplatform.googleapis.com` (Agent Platform) and\n        `storage.googleapis.com` (Cloud Storage) are enabled.\n    2.  **User \u002F Caller IAM Permissions**: explicitly confirm that the user\n        identity or default compute service account has `roles\u002Faiplatform.user`\n        and `roles\u002Fstorage.admin` (or `roles\u002Fstorage.objectAdmin`).\n    3.  **Service Agents & Roles**: explicitly report that the Agent Platform\n        Service Agent\n        (`service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com`) has\n        `roles\u002Faiplatform.serviceAgent`, and the Tuning Service Agent\n        (`service-PROJECT_NUMBER@gcp-sa-vertex-moss-ft.iam.gserviceaccount.com`\n        or `gcp-sa-vertex-tune`) has `roles\u002Faiplatform.tuningServiceAgent`.\n        Always explicitly state the verified project and region (e.g., `project:\n        \u003CPROJECT_NUMBER>, region: us-central1`) and explicitly confirm that the\n        environment is fully configured and ready for tuning.\n\n### 0.2 Location\n\nLocation handling **depends on the model category** you established in the\nworkflow decision tree. The two categories have different supported locations —\nnever apply one category's locations to the other.\n\n-   **Open models** share one fixed location set, and `global` is the\n    recommended choice.\n-   **Gemini models** differ per model and must be looked up. `global` is not\n    accepted for them today.\n\nIf the user names a location that is not valid for their model and category,\nSTOP. Respond with an error naming the requested location as unsupported, list\nthe locations that are valid, and do NOT ask for a dataset, do NOT proceed with\nany other setup step, and do NOT silently retry elsewhere.\n\n#### Open Models (RECOMMEND: `global`)\n\n**Recommend `global` and confirm it with the user.** Propose it as a single\nrecommended choice rather than making the user pick a region first, and do not\nsteer them toward a specific region instead.\n\nThese are the only locations available for open model tuning:\n\n-   `global` (the recommended choice)\n-   `us-central1`\n-   `europe-west4`\n-   `us-west1`\n-   `us-east5`\n-   `asia-southeast1`\n\nThe `global` endpoint automatically selects a supported region that has\navailable capacity, so it is the most likely to be scheduled successfully.\nPinning a region up front restricts the job to that one region's capacity, which\nis why `global` is the recommended location for open model tuning.\n\n-   **The user named a location** → use it verbatim, provided it is `global` or\n    one of the regions listed above. Do not talk them out of it.\n-   **The user asked which locations are supported** → answer the question.\n    Share the list above and say that `global` is recommended and why. Never\n    withhold it.\n-   **The user did not name a location** → propose `global` and ask them to\n    confirm it before you proceed. Say that `global` lets the service pick a\n    region with available capacity. Do NOT silently assume `global`.\n\nThe point of proposing a single choice is to avoid making region selection a\ndecision the user must resolve before anything else can happen — that ordering\nis what previously blocked people. It is not a reason to hide the list: quote it\nwhenever the user asks, and quote it when rejecting an unsupported location.\n\nFall back to an explicit region **only** in the cases below, and tell the user\nwhy you are doing so:\n\n-   **CMEK.** Customer-managed encryption keys are rejected on `global` with a\n    `FAILED_PRECONDITION` error. A CMEK-protected job must name the region that\n    holds the key.\n\n-   **Data residency.** If the user requires the job to stay in a specific\n    jurisdiction, honor their region. `global` currently runs the job in either\n    `us-central1` or `europe-west4`.\n\nIf a `global` job is accepted but then fails with a `FAILED_PRECONDITION` error\nsaying the model does not support global endpoint tuning, that model is not\nonboarded to the global endpoint yet. The model itself is still tunable:\nresubmit once in an explicit region from the list above (`us-central1` is the\nsafest choice) and tell the user why you switched.\n\n##### Working with a `global` job\n\n-   The API host stays `aiplatform.googleapis.com`. There is no\n    `global-aiplatform.googleapis.com` host.\n-   The service resolves `global` to a real region at run time. Sub-resources\n    (the tuned model, checkpoints, TensorBoard) come back with that **real**\n    region in their resource names, not `global`. Read the location out of the\n    returned resource name before using it for monitoring or deployment; never\n    assume it is still `global`.\n-   Quota is shared across regions, so pinning a region does not grant extra\n    quota.\n\n#### Gemini Models (per-model, look it up)\n\n`global` is **not accepted for Gemini tuning today** — the service rejects it at\njob creation with a `FAILED_PRECONDITION` error, so do not propose it here.\n\n**There is no single region allowlist for Gemini.** Supported tuning regions\nvary by model and by model version: some Gemini models are restricted to two\nregions while others support many more. Do NOT reuse the open model list above,\nand do NOT assume a region carries over from another Gemini model.\n\nBefore submitting, look up the chosen model in the supervised fine-tuning\ndocumentation and read its **\"Supported endpoint for model tuning\"**\nrow:\n[supervised tuning](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fmodels\u002Ftuning\u002Fsupervised-tuning)\n\n\n-   **The user asked which regions are supported** → look up that specific model\n    and tell them what the docs say. Do not answer from memory or from the open\n    model list, and do not answer for a different Gemini model.\n-   **The model's row names specific regions** → the user's region must be one\n    of them. If it is not, STOP and report the supported regions for that model.\n-   **The model's row is absent or the docs are unclear** → ask the user for the\n    region rather than guessing one.\n\nConfirm the region with the user before proceeding. Note that some Gemini models\nalso restrict CMEK and serve tuned models only on the `us` and `eu` multi-region\nendpoints, so check the same table for those limits before promising them.\n\n### 0.3 Enable APIs\n\nEnsure `aiplatform.googleapis.com` and `storage.googleapis.com` are enabled.\n\n```bash\ngcloud services enable aiplatform.googleapis.com storage.googleapis.com \\\n    --project=YOUR_PROJECT\n```\n\n### 0.4 IAM Permissions\n\nVerify the following identities have the required roles.\n\n-   **Agent Platform Service Agent**:\n    `service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com`\n-   **Managed OSS Fine Tuning Service Agent**:\n    `service-PROJECT_NUMBER@gcp-sa-vertex-moss-ft.iam.gserviceaccount.com`\n-   **User Identity**: The account running the commands.\n\n### 0.5 Python Dependencies\n\nThe scripts in this skill import `vertexai` (from `google-cloud-aiplatform`),\n`google-genai`, `google-cloud-storage`, and `datasets`.\n\n**CRITICAL AGENT INSTRUCTION:** Do **not** create a virtual environment, and do\nnot install anything before checking. A venv starts empty and hides packages the\nenvironment already provides, forcing a redundant several-minute install.\n\nProbe first and install only if the probe fails:\n\n```bash\npython3 -c \"import vertexai, google.genai, google.cloud.storage, datasets\" \\\n  || pip install -r references\u002Frequirements.txt\n```\n\nThen run every script with a plain `python3 scripts\u002F...` — no activation prefix.\n\nThe `references\u002Frequirements.txt` pins are a fallback for an environment that\ndoes not already provide these SDKs. Do not apply them on top of a working\nenvironment: they would downgrade packages other tools may share.\n\n## Phase 1: Dataset Preparation & Upload {#phase-1}\n\n### 1.0 Dataset Discovery & Confirmation\n\n-   **User-Provided Dataset Verification:** If the user specifies a dataset\n    filename or path in their prompt, verify its existence in the workspace\n    (e.g. via script execution or checking for typos).\n    *   **If the file cannot be found anywhere**, you **MUST** inform the user\n        that the dataset file does not exist or cannot be accessed. You **MUST**\n        prompt the user to provide a valid dataset path. Alternatively, if\n        candidate dataset files are found in the workspace during your search,\n        you **MUST** present the candidates to the user and ask them to select\n        one. You **MUST** stop tool execution immediately after reporting the\n        missing file or presenting candidates, and wait for the user's response.\n        Do **NOT** ask for 90\u002F10 validation split permission, and do **NOT**\n        attempt to upload the dataset before receiving a valid dataset file\n        selection from the user.\n    *   **If the file is found and verified**, proceed to Step 1.1 Formatting &\n        Validation below.\n-   **Auto-Discovery: From User Bucket:** If the user does not have a dataset\n    and no suitable alternative is found in the Hugging Face reference, offer to\n    search the user's GCS buckets for potential training data. Prioritize\n    searching for files with extensions like `.jsonl`, `.json`, `.csv`, and\n    `.parquet`. If such files are found, read the first few lines\u002Frecords of\n    each to determine if they contain text-based data suitable for tuning (e.g.,\n    prompt\u002Fcompletion pairs) that can be modified to follow\n    [Data Preparation Guide](references\u002Fdata_prep.md) and is related to the\n    tuning task requested. **DO NOT** search without prompting first.\n-   **Auto-Discovery: From Task to Huggingface:** If the user has a specific\n    task (e.g. math reasoning, coding, instruction following) or wants to use a\n    Hugging Face dataset without naming a specific one, refer to\n    [Huggingface Datasets Reference](references\u002Fhf_datasets.md) and recommend\n    matching datasets (e.g., `open-r1\u002FOpenR1-Math-220k` or\n    `AI-MO\u002FNuminaMath-TIR` for mathematical reasoning; `openai\u002Fgsm8k` is also\n    widely used). For each dataset recommended, provide some information about\n    the dataset and provide some reasonable splits, and ask the user to select\n    one. Do NOT output generic instructions telling the user to prepare and\n    upload their own data — proactively guide the interactive dataset discovery,\n    preview, and preparation flow.\n\n    > [!IMPORTANT] **CRITICAL: Ask for Confirmation and Column Selection.** Once\n    > the dataset is selected, execute Python via `run_command` to inspect the\n    > dataset using `load_dataset(..., streaming=True)`. Do not proceed with\n    > dataset preparation or upload until you perform the following steps and\n    > get user confirmation: 1. **Dataset and Split Confirmation:** Present the\n    > dataset and available splits to the user and have them confirm which to\n    > use. 2. **Column Selection (Hugging Face or Custom Datasets):** You\n    > must: - Provide a list of all available columns in the selected dataset\n    > split. - **Show a few samples from the dataset** to help the user\n    > understand the content and make the choice of columns. - Recommend which\n    > columns should be mapped to `prompt` (or user message) and `completion`\n    > (or assistant response), offering a few reasonable options if\n    > applicable. - Ask the user to confirm the column mapping or specify which\n    > columns to use.\n\n    For distillation, map only the prompt column. The teacher model writes the\n    responses, so no completion column is needed.\n\n### 1.1 Formatting & Validation\n\n-   **Conversion**: If data is in CSV, JSON, or Parquet, use\n    `scripts\u002Fprepare_dataset.py` to convert.\n-   **Validation Split Confirmation**: Except for prompt-only distillation data\n    (next bullet), whenever generating or preparing a single dataset without an\n    explicit validation set (including when generating sample chat\u002Finstruction\n    datasets or preparing training datasets), you MUST generate or process the\n    data using Python via `run_command` and **you MUST prompt the user** to seek\n    permission to split the training dataset 90\u002F10 to form a validation dataset\n    (using `--validation_split 0.1` or Python script). If they agree, proceed\n    with the split. If they decline, just use the training dataset without a\n    validation dataset. Do **NOT** offer an 80\u002F20 split; the tuning service\n    rejects it, for the reason given in\n    the\n    [Data Preparation Guide](references\u002Fdata_prep.md#sizing-the-validation-split).\n Do NOT proceed to upload the dataset or\n    submit the tuning job before asking the user about the 90\u002F10 validation\n    split!\n-   **Distillation training data**: Convert with `--prompt_only` and no\n    `--completion_col`; see the\n    [Data Preparation Guide](references\u002Fdata_prep.md#distillation-datasets). Do\n    **NOT** offer the 90\u002F10 split for a prompt-only file: the split would have\n    no responses, and a validation dataset must include them. Validation is\n    optional for distillation; use one only if the user has separate data with\n    responses, and convert it the normal way.\n-   **Validation**: If data is already in JSONL, validate it before uploading.\n    Simply having a `.jsonl` extension is not enough. You must verify that the\n    content schema is valid for tuning (e.g. correct system\u002Fuser\u002Fmodel roles).\n\n```bash\npython3 scripts\u002Fprepare_dataset.py \\\n    --input my_data.jsonl \\\n    --format \u003Cmessages|messages_gemini> \\\n    --validate_only\n```\n\n*(Use `--format messages` for open models and `--format messages_gemini` for\nGemini models.)* - Refer to [Data Preparation Guide](references\u002Fdata_prep.md)\nfor required schemas.\n\n### 1.2 Upload\n\nUpload formatted `.jsonl` files (both training and validation splits, if\napplicable) to GCS using a unique directory (e.g., with a datetime timestamp) to\navoid overwriting outputs from different runs. If the user named a bucket (e.g.,\n`gs:\u002F\u002Fmybucket`), use that bucket name EXACTLY as provided (verbatim) and NEVER\nprepend the project ID or modify the bucket name. Upload local `.jsonl` splits\nto an existing bucket via `google.cloud.storage.Client` in Python (do NOT create\na missing bucket unprompted here; see Phase 3 if a bucket needs to be proposed\nand confirmed):\n\n```python\nfrom google.cloud import storage\n\nstorage_client = storage.Client(project=PROJECT)\nbucket = storage_client.bucket(BUCKET_NAME)\nbucket.blob(f\"tuning_agent_job_{TIMESTAMP}\u002Ftrain.jsonl\").upload_from_filename(\n    \"\u002Ftmp\u002Ftrain.jsonl\"\n)\nif VAL_DATASET_PATH:\n    bucket.blob(f\"tuning_agent_job_{TIMESTAMP}\u002Fval.jsonl\").upload_from_filename(\n        VAL_DATASET_PATH\n    )\n```\n\n## Phase 2: Model Configuration & Recommendation {#phase-2}\n\nHelp the user choose the best model and parameters. **Always seek user\nconfirmation before submitting the job.**\n\n-   If the user does not specify a specific model in their prompt, calculate\n    recommendations based on the **Models Catalog**.\n-   **Prompt for Confirmation:** Present the recommended model to the user and\n    ask for their confirmation before configuring hyperparameters.\n\n### 2.1 Configuration\n\n#### For Open Models\n\n-   Recommend `tuning_mode`, `epochs`, `learning_rate`, and `adapter_size` based\n    on the [Tuning Guide](references\u002Ftuning_guide.md) and model-specific\n    baselines in the [Models Catalog](references\u002Fmodels.md).\n\n#### Verify the Live Model ID\n\nBefore submitting the job, run `scripts\u002Flist_models.py` and pick `--base_model`\nonly from its `models` output. Do not invent IDs or version numbers.\n\n```bash\npython3 scripts\u002Flist_models.py --project YOUR_PROJECT --filter gemini\n```\n\nOutput: `{\"models\": [...], \"total_count\": N, \"truncated\": bool}`.\n\n-   For Gemini, strip `google\u002F` and `@default` (e.g.\n    `google\u002Fgemini-2.5-flash@default` → `gemini-2.5-flash`); for open models,\n    pass `publisher\u002Ffamily@version` as-is.\n-   Skip Gemini variants ending in `-embedding`, `-tts`, `-image`,\n    `-computer-use`, or `-native-audio`; they are not tunable.\n-   If `truncated` is `true`, re-run with a tighter `--filter` (e.g.\n    `gemini-2.5`) before deciding the target version is unavailable.\n-   If `models` is empty, stop and ask the user.\n\n### 2.2 Calculating Cost (Open Models Only)\n\n> [!WARNING] **CRITICAL: Always Use `run_command` with\n> `scripts\u002Fcalculate_cost.py`** Do **NOT** call the `estimate_cost` ADK tool for\n> model tuning. The `estimate_cost` tool only supports specific endpoint serving\n> pricing and will fail with `Unsupported request type` on tuning requests. You\n> **MUST** call the `run_command` tool to execute Python code or\n> `scripts\u002Fcalculate_cost.py` (or\n> `\u002Fworkspace\u002Fskills\u002Fagent-platform-tuning\u002Fscripts\u002Fcalculate_cost.py`) to\n> calculate the cost. Whenever a model is chosen or the user switches models\n> (e.g. from Llama to Gemma), you **MUST** call `run_command` to calculate or\n> recalculate the cost before presenting the dry-run confirmation prompt. Always\n> report the calculated dollar figure (e.g., `Estimated tuning cost: $X.XX`) in\n> the dry-run confirmation prompt.\n\n-   We calculate the estimated cost of tuning based on the dataset and the\n    selected model in the [Models Catalog](references\u002Fmodels.md):\n\n    ```bash\n    python3 scripts\u002Fcalculate_cost.py \\\n        --input my_data.jsonl \\\n        --model MODEL_NAME \\\n        --tuning_mode TUNING_MODE \\\n        --epochs epochs\n    ```\n\n    `--model` takes either the display name (`Qwen 3 8B`) or the same resource\n    name you pass to `--base_model` (`qwen\u002Fqwen3@qwen3-8b`), so the value chosen\n    in Step 2.1 can be reused as-is.\n\n> [!NOTE] **Distillation cost:** `scripts\u002Fcalculate_cost.py` counts only the\n> text in the dataset. For a prompt-only distillation dataset it leaves out the\n> teacher's responses, which the student also trains on, and it does not include\n> the teacher model calls, which are billed separately at the teacher's\n> managed-model price. Present the figure as a lower bound.\n\n> [!NOTE] **Handling Missing Dataset Errors:** If `scripts\u002Fcalculate_cost.py`\n> fails because the dataset file (e.g. `my_data.jsonl` or `dummy_data.jsonl`)\n> cannot be found, you **MUST** inform the user that the dataset file does not\n> exist or cannot be accessed. You **MUST** prompt the user to provide a valid\n> dataset path, and stop tool execution immediately to wait for their response.\n> Do **NOT** retry or loop, do **NOT** invent a specific cost number, and do\n> **NOT** prompt for job submission approval before receiving a valid dataset\n> from the user.\n\n-   **Prompt for Confirmation:** Present the recommended hyperparameter\n    configuration and estimated cost (with the concrete dollar figure calculated\n    above) to the user and ask for their approval before proceeding to job\n    submission. Make sure to note that the estimated cost is just an estimate\n    and can vary from actual billing costs.\n\n## Phase 3: Tuning Job Execution {#phase-3-tuning-job-execution}\n\n**CRITICAL Pre-Flight Check (GCS Verification):** Before you propose a\nconfirmation prompt or submit any tuning job, you **MUST** verify that the\nspecified training dataset GCS URI (e.g. `gs:\u002F\u002Fdummy_bucket\u002Fdataset.jsonl` or\n`gs:\u002F\u002FYOUR_BUCKET\u002F...`) actually exists and is accessible. Run `gcloud storage\nls $DATASET_URI` (or `gsutil ls`).\n\n*   **If the verification fails** (e.g. `BucketNotFound`, `404`, `AccessDenied`,\n    or indicating a dummy\u002Fmissing bucket), you **MUST** inform the user that the\n    GCS bucket or dataset does not exist or cannot be accessed. You **MUST**\n    prompt the user to provide a valid GCS URI for the dataset, and stop tool\n    execution immediately to wait for their response. Do **NOT** propose a\n    confirmation prompt and do **NOT** execute any tuning scripts before\n    receiving a valid dataset URI from the user.\n*   **If the verification succeeds**, proceed to propose the confirmation prompt\n    below.\n\n### For Gemini Models\n\nSubmit the Gemini supervised fine-tuning job using the Python SDK\n(`google.genai` or `vertexai.tuning.sft`):\n\n```python\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(enterprise=True, project=PROJECT, location=LOCATION)\ntuning_job = client.tunings.tune(\n    base_model=BASE_MODEL,  # e.g. \"gemini-2.5-flash\"\n    training_dataset=types.TuningDataset(gcs_uri=TRAIN_DATASET_URI),\n    config=types.CreateTuningJobConfig(\n        epoch_count=EPOCHS,  # e.g. 3\n        learning_rate_multiplier=LEARNING_RATE_MULTIPLIER,  # e.g. 1.0\n        validation_dataset=(\n            types.TuningValidationDataset(gcs_uri=VAL_DATASET_URI)\n            if VAL_DATASET_URI\n            else None\n        ),\n    ),\n)\nprint(\"Tuning Job Resource Name:\", tuning_job.name)\n```\n\nAlternatively using `vertexai.tuning.sft`:\n\n```python\nimport vertexai\nfrom vertexai.tuning import sft\n\nvertexai.init(project=PROJECT, location=LOCATION)\njob = sft.train(\n    source_model=BASE_MODEL,\n    train_dataset=TRAIN_DATASET_URI,\n    validation_dataset=VAL_DATASET_URI,\n    epochs=EPOCHS,\n    learning_rate_multiplier=LEARNING_RATE_MULTIPLIER,\n)\nprint(\"Tuning Job Resource Name:\", job.resource_name)\n```\n\nExecute the Python script via `python3` (inline or written to\n`\u002Ftmp\u002Fsubmit_gemini_tuning.py`). Report the returned operation name or trackable\nresource identifier, and do NOT wait for the terminal state.\n\n### For Open Models\n\nSubmit the open model tuning job using `scripts\u002Ftune_open_model.py` or the\nPython SDK. Identify the model id using available models documentation\nat\n[documentation](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fmodels\u002Fopen-model-tuning#supported-models).\n\n\n`--base_model` takes a publisher model **resource name**\n(`{publisher}\u002F{model_id}@{version_id}`), not the display name shown in the\ncatalog. See \"Model Resource Name Format\" in `references\u002Fmodels.md` for the\nformat, verified examples, and how to look up a name you do not have.\n\nUsing `scripts\u002Ftune_open_model.py`:\n\n```bash\npython3 scripts\u002Ftune_open_model.py \\\n    --project YOUR_PROJECT \\\n    --location global \\\n    --base_model BASE_MODEL_ID \\\n    --train_dataset gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Ftrain.jsonl \\\n    --output_uri gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput \\\n    --epochs EPOCHS \\\n    --learning_rate LR \\\n    --tuning_mode MODE\n```\n\n*(Run `scripts\u002Ftune_open_model.py`, which maps `--tuning_mode` (`FULL` \u002F\n`PEFT_ADAPTER`) to `types.TuningMode`, sets adapter sizes and labels, and passes\n`output_uri` into `CreateTuningJobConfig(output_uri=...)`.)*\n\nThis script is open model only, and `--location` falls back to `global` if\nomitted. Always pass the location the user confirmed in section 0.2 explicitly,\nso it is visible in the command string you present for approval. If job\nsubmission fails (e.g., due to a missing or invalid parameter), report the error\nverbatim and present a fresh confirmation prompt showing the updated command\nbefore resubmitting.\n\n#### Distillation\n\nAdd `--method DISTILLATION` and `--teacher_model`. `--base_model` is the\nstudent. Use only the teacher and student models listed under \"Distillation\" in\n`references\u002Fmodels.md`.\n\n```bash\npython3 scripts\u002Ftune_open_model.py \\\n    --project YOUR_PROJECT \\\n    --location global \\\n    --method DISTILLATION \\\n    --teacher_model TEACHER_MODEL_ID \\\n    --base_model STUDENT_MODEL_ID \\\n    --train_dataset gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Fprompts.jsonl \\\n    --output_uri gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput \\\n    --epochs EPOCHS \\\n    --learning_rate LR \\\n    --tuning_mode MODE\n```\n\n-   The training dataset can contain prompts only; the teacher writes the\n    responses. A validation dataset must include responses.\n-   Teacher calls draw on the project's quota for the teacher model.\n-   The job writes the teacher-labeled data to\n    `distillation_labelled_dataset.jsonl` in the output directory.\n\n> [!WARNING] **`--output_uri` \u002F `output_uri` is required for open models.** The\n> Python SDK declares it as `output_uri: Optional[str] = None`, but the tuning\n> backend rejects open model jobs that omit it with `INVALID_ARGUMENT: The\n> output_uri field is required for this model.` Treat the SDK's \"optional\"\n> signature as wrong here and always pass a GCS destination (`output_uri`) both\n> in CLI flags and in `CreateTuningJobConfig(output_uri=...)`.\n\nBecause the flag is mandatory, you must establish where the tuned model is\nwritten before you can submit. **Never invent a bucket name, derive one from the\nproject number, or run `gcloud storage buckets create` unprompted.** Creating a\nbucket is a mutating action and is subject to the Tier M confirmation policy\nbelow.\n\n-   **The user named a bucket or URI** → use it EXACTLY as specified by the user\n    (verbatim), appending a unique per-job directory as in section 1.2.\n    **CRITICAL:** NEVER alter, prefix, or prepend the project number or anything\n    else to a user-specified bucket name! Even if `gcloud storage buckets list`\n    shows an existing bucket with a project-prefixed name (e.g.\n    `gs:\u002F\u002FPROJECT-mybucket` when the user asked for `gs:\u002F\u002Fmybucket`), you MUST\n    use the user's exact bucket name `gs:\u002F\u002Fmybucket` verbatim. Never silently\n    substitute an existing bucket.\n-   **A bucket was already used for the dataset upload in section 1.2** →\n    propose reusing it for the output and ask the user to confirm.\n-   **Neither or user states they have no bucket** → Check existing buckets in\n    the project (`gcloud storage buckets list --project=PROJECT`) or offer to\n    create a dedicated bucket. When proposing a bucket to create, ensure the\n    bucket name is unique by including a unique suffix or timestamp (e.g.\n    `gs:\u002F\u002FPROJECT-tuning-$(date +%s)` or\n    `gs:\u002F\u002FPROJECT-tuning-artifacts-\u003Ctimestamp>` in LOCATION) to prevent HTTP 409\n    collisions with previously created buckets. Propose the destination bucket\n    in your configuration dry-run preview and ask the user for confirmation\n    before proceeding.\n\n> [!IMPORTANT] **Interactive Confirmation Required (Tier M):** Before proceeding\n> with job submission, you **MUST** present the proposed command string showing\n> all literal flags (including `--output_uri` for open models and estimated\n> cost) in a confirmation prompt to the user with 'Yes' and 'No' options. Do NOT\n> execute the tuning job submission via `run_command` in the same turn as\n> presenting the confirmation prompt; yield immediately for the user's reply.\n\n## Phase 4: Monitoring {#phase-4-monitoring}\n\nMonitor the job via the Cloud Console link provided in the script output.\n`--location` is required and must be the same location you submitted with: an\nopen model job submitted on `global` is polled with `--location global`, even\nthough the work runs in a real region behind the scenes.\n\nAdditionally, ask the user if they want you to monitor the job status for them\nin the background. If they agree, execute `scripts\u002Fmonitor_tuning_job.py` as a\nbackground task to periodically poll the job status and notify the user to show\nthe status. If the user declines, leave it completely to the user to check on\nthe status.\n\n## Phase 5: Model Deployment {#phase-5-model-deployment}\n\nOnce the tuning job is `SUCCEEDED`, deploy the tuned checkpoint by registering\nit in Model Registry with the base model's serving container, then deploying\nthat registered model to a new endpoint. **CRITICAL:** If the tuning job is\nstill pending\u002Frunning (`tuned_model_name` is `None` or not yet generated),\n**NEVER deploy the untuned base model (e.g., `google\u002Fgemma3@...`) as a\nsubstitute!** Instead, explicitly inform the user that deployment of the tuned\nmodel cannot proceed until the tuning job finishes (`JOB_STATE_SUCCEEDED`) and\ngenerates the tuned model artifact.\n\nDeployment requires a real region — `global` is not valid here. If the job ran\non `global`, read the region out of the tuned model's resource name\n(`projects\u002F...\u002Flocations\u002F\u003CREGION>\u002Fmodels\u002F...`) and deploy there; do not guess.\n\nIf a deploy step is rejected for quota, report the API's error verbatim.\n\n```bash\n#!\u002Fbin\u002Fbash\n# Deploy a tuned checkpoint by registering it in Model Registry with the base\n# model's serving container, then deploying to a new endpoint. All four gcloud\n# calls hit aiplatform.googleapis.com.\nset -euo pipefail\nPROJECT_ID=\"YOUR_PROJECT\"\nLOCATION_ID=\"YOUR_LOCATION\"          # e.g. us-central1; NOT global.\nBASE_MODEL=\"\u003CPUBLISHER>\u002F\u003CFAMILY>@\u003CVERSION-ID>\"   # the base model you tuned; e.g. google\u002Fgemma3@gemma-3-4b-it\nARTIFACTS=\"gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput\u002Fpostprocess\u002Fnode-0\u002Fcheckpoints\u002Ffinal\"\nDISPLAY_NAME=\"my-tuned-model\"\n# Explicit IDs, so no step can pick up an older model or endpoint that shares\n# the display name.\nRUN_ID=\"tuned-$(date +%Y%m%d-%H%M%S)\"\n\n# 1. List the base model's verified deployment configs and pick the row that\n#    fits the user-approved cost\u002Fperf. CONFIG_INDEX is its 0-based table row.\ngcloud ai model-garden models list-deployment-config \\\n    --model=\"$BASE_MODEL\" \\\n    --project=\"$PROJECT_ID\"\nCONFIG_INDEX=\"\u003Cpick from step 1>\"\n\n# Turn that row into gcloud flags files. The table shows only the image, but\n# the container also needs the row's args, env, ports and routes; its\n# --model\u002F--model-path arg points at the base weights and is repointed at\n# $ARTIFACTS.\ngcloud ai model-garden models list-deployment-config \\\n    --model=\"$BASE_MODEL\" \\\n    --project=\"$PROJECT_ID\" \\\n    --format=json | python3 -c '\nimport json, sys\nidx, artifacts = int(sys.argv[1]), sys.argv[2]\ncfg = json.load(sys.stdin)[idx]\nspec = dict(cfg[\"containerSpec\"])\nargs, repointed = [], 0\nfor a in spec.pop(\"args\", []):\n    flag = a.split(\"=\", 1)[0]\n    if flag in (\"--model\", \"--model-path\"):\n        a, repointed = flag + \"=\" + artifacts, repointed + 1\n    args.append(a)\nif repointed != 1:\n    sys.exit(\"expected exactly one --model\u002F--model-path arg, found %d\" % repointed)\nupload = {\"--container-image-uri\": spec.pop(\"imageUri\"), \"--container-args\": args}\nif \"command\" in spec:\n    upload[\"--container-command\"] = spec.pop(\"command\")\nif \"env\" in spec:\n    upload[\"--container-env-vars\"] = {e[\"name\"]: e[\"value\"] for e in spec.pop(\"env\")}\nif \"ports\" in spec:\n    upload[\"--container-ports\"] = [str(p[\"containerPort\"]) for p in spec.pop(\"ports\")]\nfor key, flag in ((\"predictRoute\", \"--container-predict-route\"),\n                  (\"healthRoute\", \"--container-health-route\"),\n                  (\"sharedMemorySizeMb\", \"--container-shared-memory-size-mb\")):\n    if key in spec:\n        upload[flag] = str(spec.pop(key))\nif \"deploymentTimeout\" in spec:  # e.g. \"7200s\"\n    upload[\"--container-deployment-timeout-seconds\"] = str(\n        int(float(spec.pop(\"deploymentTimeout\").rstrip(\"s\"))))\nif spec:\n    sys.exit(\"unhandled containerSpec fields: %s\" % sorted(spec))\nm = cfg[\"dedicatedResources\"][\"machineSpec\"]\ndeploy = {\"--machine-type\": m[\"machineType\"]}\nif \"acceleratorType\" in m:\n    deploy[\"--accelerator\"] = {\n        \"type\": m[\"acceleratorType\"].lower().replace(\"_\", \"-\"),\n        \"count\": str(m.get(\"acceleratorCount\", 1))}\njson.dump(upload, open(\"\u002Ftmp\u002Fupload_flags.yaml\", \"w\"), indent=1)\njson.dump(deploy, open(\"\u002Ftmp\u002Fdeploy_flags.yaml\", \"w\"), indent=1)\n' \"$CONFIG_INDEX\" \"$ARTIFACTS\"\ncat \u002Ftmp\u002Fupload_flags.yaml \u002Ftmp\u002Fdeploy_flags.yaml\n\n# 2. Register the tuned checkpoint in Model Registry with the base model's\n#    serving container. Blocks until the upload finishes.\ngcloud ai models upload \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --model-id=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\" \\\n    --artifact-uri=\"$ARTIFACTS\" \\\n    --flags-file=\u002Ftmp\u002Fupload_flags.yaml\n\n# 3. Create a dedicated endpoint.\ngcloud ai endpoints create \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --endpoint-id=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\"\n\n# 4. Deploy the registered model to the endpoint.\ngcloud ai endpoints deploy-model \"$RUN_ID\" \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --model=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\" \\\n    --flags-file=\u002Ftmp\u002Fdeploy_flags.yaml\n```\n\n`set -e` stops the sequence at the first failed step. Step 4 blocks until the\nmodel is serving, which can take 20 minutes or more; its progress output prints\nthe operation name, `projects\u002F\u003CPROJECT>\u002Flocations\u002F\u003CREGION>\u002Foperations\u002F\u003COP_ID>`.\nPass that whole name to `gcloud ai operations describe \u003COPERATION_NAME>` for\nstatus checks from another shell: a bare `\u003COP_ID>` works only when gcloud has a\nproject.\n\n> [!IMPORTANT] **Interactive Confirmation Required (Tier M):** Before proceeding\n> with deployment, you **MUST** present the three write commands (steps 2-4)\n> with all literal values filled in, plus the generated flags files (container\n> image, args, machine type, accelerator config), in a confirmation prompt to\n> the user with 'Yes' and 'No' options. Do NOT execute the deployment commands\n> via `run_command` in the same turn as presenting the confirmation prompt;\n> yield immediately for the user's reply.\n\nRefer to [Models Catalog](references\u002Fmodels.md) for hardware recommendations for\nspecific open models.\n\n## Resources\n\n-   [Data Preparation Guide](references\u002Fdata_prep.md)\n-   [Models Catalog](references\u002Fmodels.md)\n-   [Tuning Guide](references\u002Ftuning_guide.md)\n-   `scripts\u002Fprepare_dataset.py`: Data conversion & validation.\n-   `scripts\u002Ftune_open_model.py`: Open model tuning job submission.\n","# Agent Platform Model Tuning\n\n## Attribution\n\n**MUST READ**: Before executing any `gcloud`, REST API, or MCP command, you MUST\nread [Usage Attribution](references\u002Fattribution.md). For commands executed\ndirectly in the terminal (never in generated scripts, Terraform, or user code),\nprefix `gcloud` with `CLOUDSDK_METRICS_REQUEST_ATTRIBUTION` and append the\nattribution suffix to `User-Agent` headers:\n\n```text\nCLOUDSDK_METRICS_REQUEST_ATTRIBUTION=\"agent-skills\u002F1.0 (skill:agent-platform-tuning)\"\nUser-Agent: \u003Cexisting_user_agent> agent-skills\u002F1.0 (skill:agent-platform-tuning)\n```\n\n## Overview\n\nThis skill provides procedural knowledge for fine-tuning Large Language Models\n(both Open Models and Gemini Models) using Agent Platform's tuning service. It\ncovers the entire lifecycle from environment setup and data preparation to job\nconfiguration, monitoring, and deployment.\n\n## Workflow Decision Tree\n\n1.  **Project & Region Verification Check**: Has the user provided the Google\n    Cloud project and region?\n\n    -   **No** → **STOP tool execution immediately**. Do NOT run verification\n        commands (`gcloud services list`, `gcloud projects get-iam-policy`), do\n        NOT create resources, and do NOT begin dataset preparation. Prompt the\n        user to specify or confirm the project and region (e.g. \"Could you\n        please specify which Google Cloud project and region you would like to\n        use?\").\n        -   If the user's inquiry is solely to check or verify environment\n            readiness (APIs, IAM, service agents), ask ONLY for the project and\n            region. Do NOT ask for the model category.\n        -   If the user is requesting a tuning workflow and also omitted whether\n            they want to tune an Open Model or a Gemini Model, you may ask both\n            questions together.\n    -   **Yes** → Proceed.\n\n2.  **Model Category Identification**: Has the user explicitly stated whether\n    they want to tune an **Open Model** or a **Gemini Model**?\n\n    -   **No** →\n        -   **EXCEPTION for Environment Verification Inquiries:** If the user is\n            only asking to check or verify that the environment, APIs, IAM\n            permissions, or service agents are ready for tuning, do NOT ask for\n            the model category. Verify the environment once the project and\n            region are known and confirm readiness.\n        -   Otherwise, **STOP tool execution**. Ask the user if they want to\n            tune an Open Model or a Gemini Model. **General Setup and\n            Prerequisite Inquiries (e.g., \"What environment setup is\n            needed?\"):** If the user asks what environment setup, prerequisites,\n            APIs, or permissions are needed to start fine-tuning, and has not\n            yet chosen a model category:\n        *   Describe the setup requirements (APIs, IAM permissions\u002Fservice\n            agents, and Python SDKs).\n        *   Regarding Cloud Storage: state that an existing Cloud Storage bucket\n            is needed for datasets and artifacts (e.g.,\n            `gs:\u002F\u002F\u003Cexisting-bucket>`). **CRITICAL:** Do NOT instruct the user to\n            create a bucket, do NOT output a `gcloud storage buckets create`\n            command in setup instructions, and do NOT assume a non-existent\n            bucket exists (users may not have bucket creation permissions and\n            will provide their own existing bucket).\n        *   You MUST explicitly conclude your prose response with the direct\n            question: **\"Would you like to tune an Open Model or a Gemini\n            Model?\"** (do NOT bury this choice inside a bulleted \"Suggested Next\n            Steps\" list). Never provide setup instructions without explicitly\n            asking this question in prose. (Note: if they ask to actively check\n            or verify a project whose ID or region is missing, ask for the\n            project and region first without running tool calls).\n    -   If the user provides a specific tuning purpose, you should recommend\n        three models: one Open Model, one Gemini Model, and a third generally\n        recommended choice. Briefly list the pros and cons of each (e.g., Gemini\n        models might be more expensive, etc.). **CRITICAL:** You must read\n        `references\u002Fmodels.md` during this step and only recommend models\n        explicitly listed in that catalog. Never recommend uncataloged or\n        unsupported models like `google\u002Fgemma-2-9b-it`, `gemma-2`, or `Mistral`\n        — only recommend supported models such as Gemma 3\n        (`google\u002Fgemma3@gemma-3-12b-it`), Qwen 3 (`qwen\u002Fqwen3@qwen3-8b`), or\n        Llama 3.1 (`meta\u002Fllama3_1@llama-3.1-8b`). For Gemini models, ONLY\n        recommend `gemini-2.5-flash` (recommended for general\u002Fcoding\u002Fchat) or\n        `gemini-2.5-pro`. Never recommend `gemini-1.5-flash-002`,\n        `gemini-1.5-pro-002`, or `gemini-1.5-flash`, which are deprecated and\n        unsupported by the tuning service. If the user names a model that is not\n        in the catalog, follow the fallback rule in that catalog. Do not proceed\n        with model configuration until the category is confirmed.\n    -   **Distillation requests**: If the user asks to distill a model, names a\n        teacher model, or wants a smaller model to learn from a larger one, this\n        is an Open Model workflow using distillation. Read \"Distillation\" in\n        `references\u002Fmodels.md` and use only the teacher and student models\n        listed there.\n    -   **Yes** → Proceed.\n\n3.  **Environment Check**: Has the environment (Auth, APIs, IAM, Venv) been\n    initialized?\n\n    -   **No** → Go to [Phase 0: Environment & IAM Setup](#phase-0).\n    -   **Yes** → Proceed.\n\n4.  **Dataset Status**: Is the dataset ready in JSONL format, **is its structure\n    valid for tuning**, and is it uploaded to Google Cloud Storage?\n\n    ```\n    -   **No** → Go to [Phase 1: Dataset Preparation & Upload](#phase-1).\n    -   **Yes** → Proceed.\n    ```\n\n5.  **Column Selection Confirmation**: Have you presented the columns to the\n    user and confirmed the mapping?\n\n    -   **No** → **STOP**. You must show samples and get user confirmation on\n        column mapping as described in Phase 1.0 before proceeding.\n    -   **Yes** → Proceed.\n\n6.  **Configuration**: Has the user provided the target model and\n    hyperparameters, or explicitly agreed to your recommendations?\n\n    -   **No** → Go to\n        [Phase 2: Model Configuration & Recommendation](#phase-2).\n    -   **Yes** → Proceed.\n\n7.  **Job Status**: Has the tuning job been submitted?\n\n    ```\n    -   **No** → Go to\n        [Phase 3: Tuning Job Execution](#phase-3-tuning-job-execution).\n    -   **Yes** → Proceed.\n    ```\n\n8.  **Job Completion**: Is the tuning job complete?\n\n    ```\n    -   **No** → Go to [Phase 4: Monitoring](#phase-4-monitoring).\n    -   **Yes** → Proceed.\n    ```\n\n9.  **Deployment**: Has the tuned model been deployed (if required)?\n\n    ```\n    -   **No** → Go to [Phase 5: Model Deployment](#phase-5-model-deployment).\n    -   **Yes** → Task Complete.\n    ```\n\n## Phase 0: Environment & IAM Setup {#phase-0}\n\nEnsure the foundational environment is ready before proceeding.\n\n### 0.1 Authentication & Project Context\n\n-   Check if `gcloud` CLI is installed. If it is not installed, prompt the user\n    for permission to install it before proceeding. If it is installed, update\n    it:\n\n```bash\ngcloud components update --quiet > \u002Fdev\u002Fnull 2>&1\n```\n\n-   Verify `gcloud auth list`. If not authenticated, run `gcloud auth login`.\n-   **Project & Region Grounding**: Check if the user specified their GCP\n    project and region in their prompt. If the user's prompt omits either the\n    project or the region (e.g., in an environment verification or setup\n    request), you **MUST STOP tool execution immediately without running any\n    bash or gcloud commands** (do NOT call `gcloud config get project` or\n    `gcloud services list`). Ask the user to provide their project ID\u002Fnumber and\n    region.\n-   Once the project and region are provided or confirmed by the user, verify\n    that `gcloud` is authenticated and execute read-only checks to verify the\n    environment. When reporting environment readiness, your summary MUST\n    explicitly detail the status of all three categories:\n    1.  **Required APIs**: explicitly report that both\n        `aiplatform.googleapis.com` (Agent Platform) and\n        `storage.googleapis.com` (Cloud Storage) are enabled.\n    2.  **User \u002F Caller IAM Permissions**: explicitly confirm that the user\n        identity or default compute service account has `roles\u002Faiplatform.user`\n        and `roles\u002Fstorage.admin` (or `roles\u002Fstorage.objectAdmin`).\n    3.  **Service Agents & Roles**: explicitly report that the Agent Platform\n        Service Agent\n        (`service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com`) has\n        `roles\u002Faiplatform.serviceAgent`, and the Tuning Service Agent\n        (`service-PROJECT_NUMBER@gcp-sa-vertex-moss-ft.iam.gserviceaccount.com`\n        or `gcp-sa-vertex-tune`) has `roles\u002Faiplatform.tuningServiceAgent`.\n        Always explicitly state the verified project and region (e.g., `project:\n        \u003CPROJECT_NUMBER>, region: us-central1`) and explicitly confirm that the\n        environment is fully configured and ready for tuning.\n\n### 0.2 Location\n\nLocation handling **depends on the model category** you established in the\nworkflow decision tree. The two categories have different supported locations —\nnever apply one category's locations to the other.\n\n-   **Open models** share one fixed location set, and `global` is the\n    recommended choice.\n-   **Gemini models** differ per model and must be looked up. `global` is not\n    accepted for them today.\n\nIf the user names a location that is not valid for their model and category,\nSTOP. Respond with an error naming the requested location as unsupported, list\nthe locations that are valid, and do NOT ask for a dataset, do NOT proceed with\nany other setup step, and do NOT silently retry elsewhere.\n\n#### Open Models (RECOMMEND: `global`)\n\n**Recommend `global` and confirm it with the user.** Propose it as a single\nrecommended choice rather than making the user pick a region first, and do not\nsteer them toward a specific region instead.\n\nThese are the only locations available for open model tuning:\n\n-   `global` (the recommended choice)\n-   `us-central1`\n-   `europe-west4`\n-   `us-west1`\n-   `us-east5`\n-   `asia-southeast1`\n\nThe `global` endpoint automatically selects a supported region that has\navailable capacity, so it is the most likely to be scheduled successfully.\nPinning a region up front restricts the job to that one region's capacity, which\nis why `global` is the recommended location for open model tuning.\n\n-   **The user named a location** → use it verbatim, provided it is `global` or\n    one of the regions listed above. Do not talk them out of it.\n-   **The user asked which locations are supported** → answer the question.\n    Share the list above and say that `global` is recommended and why. Never\n    withhold it.\n-   **The user did not name a location** → propose `global` and ask them to\n    confirm it before you proceed. Say that `global` lets the service pick a\n    region with available capacity. Do NOT silently assume `global`.\n\nThe point of proposing a single choice is to avoid making region selection a\ndecision the user must resolve before anything else can happen — that ordering\nis what previously blocked people. It is not a reason to hide the list: quote it\nwhenever the user asks, and quote it when rejecting an unsupported location.\n\nFall back to an explicit region **only** in the cases below, and tell the user\nwhy you are doing so:\n\n-   **CMEK.** Customer-managed encryption keys are rejected on `global` with a\n    `FAILED_PRECONDITION` error. A CMEK-protected job must name the region that\n    holds the key.\n\n-   **Data residency.** If the user requires the job to stay in a specific\n    jurisdiction, honor their region. `global` currently runs the job in either\n    `us-central1` or `europe-west4`.\n\nIf a `global` job is accepted but then fails with a `FAILED_PRECONDITION` error\nsaying the model does not support global endpoint tuning, that model is not\nonboarded to the global endpoint yet. The model itself is still tunable:\nresubmit once in an explicit region from the list above (`us-central1` is the\nsafest choice) and tell the user why you switched.\n\n##### Working with a `global` job\n\n-   The API host stays `aiplatform.googleapis.com`. There is no\n    `global-aiplatform.googleapis.com` host.\n-   The service resolves `global` to a real region at run time. Sub-resources\n    (the tuned model, checkpoints, TensorBoard) come back with that **real**\n    region in their resource names, not `global`. Read the location out of the\n    returned resource name before using it for monitoring or deployment; never\n    assume it is still `global`.\n-   Quota is shared across regions, so pinning a region does not grant extra\n    quota.\n\n#### Gemini Models (per-model, look it up)\n\n`global` is **not accepted for Gemini tuning today** — the service rejects it at\njob creation with a `FAILED_PRECONDITION` error, so do not propose it here.\n\n**There is no single region allowlist for Gemini.** Supported tuning regions\nvary by model and by model version: some Gemini models are restricted to two\nregions while others support many more. Do NOT reuse the open model list above,\nand do NOT assume a region carries over from another Gemini model.\n\nBefore submitting, look up the chosen model in the supervised fine-tuning\ndocumentation and read its **\"Supported endpoint for model tuning\"**\nrow:\n[supervised tuning](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fmodels\u002Ftuning\u002Fsupervised-tuning)\n\n\n-   **The user asked which regions are supported** → look up that specific model\n    and tell them what the docs say. Do not answer from memory or from the open\n    model list, and do not answer for a different Gemini model.\n-   **The model's row names specific regions** → the user's region must be one\n    of them. If it is not, STOP and report the supported regions for that model.\n-   **The model's row is absent or the docs are unclear** → ask the user for the\n    region rather than guessing one.\n\nConfirm the region with the user before proceeding. Note that some Gemini models\nalso restrict CMEK and serve tuned models only on the `us` and `eu` multi-region\nendpoints, so check the same table for those limits before promising them.\n\n### 0.3 Enable APIs\n\nEnsure `aiplatform.googleapis.com` and `storage.googleapis.com` are enabled.\n\n```bash\ngcloud services enable aiplatform.googleapis.com storage.googleapis.com \\\n    --project=YOUR_PROJECT\n```\n\n### 0.4 IAM Permissions\n\nVerify the following identities have the required roles.\n\n-   **Agent Platform Service Agent**:\n    `service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com`\n-   **Managed OSS Fine Tuning Service Agent**:\n    `service-PROJECT_NUMBER@gcp-sa-vertex-moss-ft.iam.gserviceaccount.com`\n-   **User Identity**: The account running the commands.\n\n### 0.5 Python Dependencies\n\nThe scripts in this skill import `vertexai` (from `google-cloud-aiplatform`),\n`google-genai`, `google-cloud-storage`, and `datasets`.\n\n**CRITICAL AGENT INSTRUCTION:** Do **not** create a virtual environment, and do\nnot install anything before checking. A venv starts empty and hides packages the\nenvironment already provides, forcing a redundant several-minute install.\n\nProbe first and install only if the probe fails:\n\n```bash\npython3 -c \"import vertexai, google.genai, google.cloud.storage, datasets\" \\\n  || pip install -r references\u002Frequirements.txt\n```\n\nThen run every script with a plain `python3 scripts\u002F...` — no activation prefix.\n\nThe `references\u002Frequirements.txt` pins are a fallback for an environment that\ndoes not already provide these SDKs. Do not apply them on top of a working\nenvironment: they would downgrade packages other tools may share.\n\n## Phase 1: Dataset Preparation & Upload {#phase-1}\n\n### 1.0 Dataset Discovery & Confirmation\n\n-   **User-Provided Dataset Verification:** If the user specifies a dataset\n    filename or path in their prompt, verify its existence in the workspace\n    (e.g. via script execution or checking for typos).\n    *   **If the file cannot be found anywhere**, you **MUST** inform the user\n        that the dataset file does not exist or cannot be accessed. You **MUST**\n        prompt the user to provide a valid dataset path. Alternatively, if\n        candidate dataset files are found in the workspace during your search,\n        you **MUST** present the candidates to the user and ask them to select\n        one. You **MUST** stop tool execution immediately after reporting the\n        missing file or presenting candidates, and wait for the user's response.\n        Do **NOT** ask for 90\u002F10 validation split permission, and do **NOT**\n        attempt to upload the dataset before receiving a valid dataset file\n        selection from the user.\n    *   **If the file is found and verified**, proceed to Step 1.1 Formatting &\n        Validation below.\n-   **Auto-Discovery: From User Bucket:** If the user does not have a dataset\n    and no suitable alternative is found in the Hugging Face reference, offer to\n    search the user's GCS buckets for potential training data. Prioritize\n    searching for files with extensions like `.jsonl`, `.json`, `.csv`, and\n    `.parquet`. If such files are found, read the first few lines\u002Frecords of\n    each to determine if they contain text-based data suitable for tuning (e.g.,\n    prompt\u002Fcompletion pairs) that can be modified to follow\n    [Data Preparation Guide](references\u002Fdata_prep.md) and is related to the\n    tuning task requested. **DO NOT** search without prompting first.\n-   **Auto-Discovery: From Task to Huggingface:** If the user has a specific\n    task (e.g. math reasoning, coding, instruction following) or wants to use a\n    Hugging Face dataset without naming a specific one, refer to\n    [Huggingface Datasets Reference](references\u002Fhf_datasets.md) and recommend\n    matching datasets (e.g., `open-r1\u002FOpenR1-Math-220k` or\n    `AI-MO\u002FNuminaMath-TIR` for mathematical reasoning; `openai\u002Fgsm8k` is also\n    widely used). For each dataset recommended, provide some information about\n    the dataset and provide some reasonable splits, and ask the user to select\n    one. Do NOT output generic instructions telling the user to prepare and\n    upload their own data — proactively guide the interactive dataset discovery,\n    preview, and preparation flow.\n\n    > [!IMPORTANT] **CRITICAL: Ask for Confirmation and Column Selection.** Once\n    > the dataset is selected, execute Python via `run_command` to inspect the\n    > dataset using `load_dataset(..., streaming=True)`. Do not proceed with\n    > dataset preparation or upload until you perform the following steps and\n    > get user confirmation: 1. **Dataset and Split Confirmation:** Present the\n    > dataset and available splits to the user and have them confirm which to\n    > use. 2. **Column Selection (Hugging Face or Custom Datasets):** You\n    > must: - Provide a list of all available columns in the selected dataset\n    > split. - **Show a few samples from the dataset** to help the user\n    > understand the content and make the choice of columns. - Recommend which\n    > columns should be mapped to `prompt` (or user message) and `completion`\n    > (or assistant response), offering a few reasonable options if\n    > applicable. - Ask the user to confirm the column mapping or specify which\n    > columns to use.\n\n    For distillation, map only the prompt column. The teacher model writes the\n    responses, so no completion column is needed.\n\n### 1.1 Formatting & Validation\n\n-   **Conversion**: If data is in CSV, JSON, or Parquet, use\n    `scripts\u002Fprepare_dataset.py` to convert.\n-   **Validation Split Confirmation**: Except for prompt-only distillation data\n    (next bullet), whenever generating or preparing a single dataset without an\n    explicit validation set (including when generating sample chat\u002Finstruction\n    datasets or preparing training datasets), you MUST generate or process the\n    data using Python via `run_command` and **you MUST prompt the user** to seek\n    permission to split the training dataset 90\u002F10 to form a validation dataset\n    (using `--validation_split 0.1` or Python script). If they agree, proceed\n    with the split. If they decline, just use the training dataset without a\n    validation dataset. Do **NOT** offer an 80\u002F20 split; the tuning service\n    rejects it, for the reason given in\n    the\n    [Data Preparation Guide](references\u002Fdata_prep.md#sizing-the-validation-split).\n Do NOT proceed to upload the dataset or\n    submit the tuning job before asking the user about the 90\u002F10 validation\n    split!\n-   **Distillation training data**: Convert with `--prompt_only` and no\n    `--completion_col`; see the\n    [Data Preparation Guide](references\u002Fdata_prep.md#distillation-datasets). Do\n    **NOT** offer the 90\u002F10 split for a prompt-only file: the split would have\n    no responses, and a validation dataset must include them. Validation is\n    optional for distillation; use one only if the user has separate data with\n    responses, and convert it the normal way.\n-   **Validation**: If data is already in JSONL, validate it before uploading.\n    Simply having a `.jsonl` extension is not enough. You must verify that the\n    content schema is valid for tuning (e.g. correct system\u002Fuser\u002Fmodel roles).\n\n```bash\npython3 scripts\u002Fprepare_dataset.py \\\n    --input my_data.jsonl \\\n    --format \u003Cmessages|messages_gemini> \\\n    --validate_only\n```\n\n*(Use `--format messages` for open models and `--format messages_gemini` for\nGemini models.)* - Refer to [Data Preparation Guide](references\u002Fdata_prep.md)\nfor required schemas.\n\n### 1.2 Upload\n\nUpload formatted `.jsonl` files (both training and validation splits, if\napplicable) to GCS using a unique directory (e.g., with a datetime timestamp) to\navoid overwriting outputs from different runs. If the user named a bucket (e.g.,\n`gs:\u002F\u002Fmybucket`), use that bucket name EXACTLY as provided (verbatim) and NEVER\nprepend the project ID or modify the bucket name. Upload local `.jsonl` splits\nto an existing bucket via `google.cloud.storage.Client` in Python (do NOT create\na missing bucket unprompted here; see Phase 3 if a bucket needs to be proposed\nand confirmed):\n\n```python\nfrom google.cloud import storage\n\nstorage_client = storage.Client(project=PROJECT)\nbucket = storage_client.bucket(BUCKET_NAME)\nbucket.blob(f\"tuning_agent_job_{TIMESTAMP}\u002Ftrain.jsonl\").upload_from_filename(\n    \"\u002Ftmp\u002Ftrain.jsonl\"\n)\nif VAL_DATASET_PATH:\n    bucket.blob(f\"tuning_agent_job_{TIMESTAMP}\u002Fval.jsonl\").upload_from_filename(\n        VAL_DATASET_PATH\n    )\n```\n\n## Phase 2: Model Configuration & Recommendation {#phase-2}\n\nHelp the user choose the best model and parameters. **Always seek user\nconfirmation before submitting the job.**\n\n-   If the user does not specify a specific model in their prompt, calculate\n    recommendations based on the **Models Catalog**.\n-   **Prompt for Confirmation:** Present the recommended model to the user and\n    ask for their confirmation before configuring hyperparameters.\n\n### 2.1 Configuration\n\n#### For Open Models\n\n-   Recommend `tuning_mode`, `epochs`, `learning_rate`, and `adapter_size` based\n    on the [Tuning Guide](references\u002Ftuning_guide.md) and model-specific\n    baselines in the [Models Catalog](references\u002Fmodels.md).\n\n#### Verify the Live Model ID\n\nBefore submitting the job, run `scripts\u002Flist_models.py` and pick `--base_model`\nonly from its `models` output. Do not invent IDs or version numbers.\n\n```bash\npython3 scripts\u002Flist_models.py --project YOUR_PROJECT --filter gemini\n```\n\nOutput: `{\"models\": [...], \"total_count\": N, \"truncated\": bool}`.\n\n-   For Gemini, strip `google\u002F` and `@default` (e.g.\n    `google\u002Fgemini-2.5-flash@default` → `gemini-2.5-flash`); for open models,\n    pass `publisher\u002Ffamily@version` as-is.\n-   Skip Gemini variants ending in `-embedding`, `-tts`, `-image`,\n    `-computer-use`, or `-native-audio`; they are not tunable.\n-   If `truncated` is `true`, re-run with a tighter `--filter` (e.g.\n    `gemini-2.5`) before deciding the target version is unavailable.\n-   If `models` is empty, stop and ask the user.\n\n### 2.2 Calculating Cost (Open Models Only)\n\n> [!WARNING] **CRITICAL: Always Use `run_command` with\n> `scripts\u002Fcalculate_cost.py`** Do **NOT** call the `estimate_cost` ADK tool for\n> model tuning. The `estimate_cost` tool only supports specific endpoint serving\n> pricing and will fail with `Unsupported request type` on tuning requests. You\n> **MUST** call the `run_command` tool to execute Python code or\n> `scripts\u002Fcalculate_cost.py` (or\n> `\u002Fworkspace\u002Fskills\u002Fagent-platform-tuning\u002Fscripts\u002Fcalculate_cost.py`) to\n> calculate the cost. Whenever a model is chosen or the user switches models\n> (e.g. from Llama to Gemma), you **MUST** call `run_command` to calculate or\n> recalculate the cost before presenting the dry-run confirmation prompt. Always\n> report the calculated dollar figure (e.g., `Estimated tuning cost: $X.XX`) in\n> the dry-run confirmation prompt.\n\n-   We calculate the estimated cost of tuning based on the dataset and the\n    selected model in the [Models Catalog](references\u002Fmodels.md):\n\n    ```bash\n    python3 scripts\u002Fcalculate_cost.py \\\n        --input my_data.jsonl \\\n        --model MODEL_NAME \\\n        --tuning_mode TUNING_MODE \\\n        --epochs epochs\n    ```\n\n    `--model` takes either the display name (`Qwen 3 8B`) or the same resource\n    name you pass to `--base_model` (`qwen\u002Fqwen3@qwen3-8b`), so the value chosen\n    in Step 2.1 can be reused as-is.\n\n> [!NOTE] **Distillation cost:** `scripts\u002Fcalculate_cost.py` counts only the\n> text in the dataset. For a prompt-only distillation dataset it leaves out the\n> teacher's responses, which the student also trains on, and it does not include\n> the teacher model calls, which are billed separately at the teacher's\n> managed-model price. Present the figure as a lower bound.\n\n> [!NOTE] **Handling Missing Dataset Errors:** If `scripts\u002Fcalculate_cost.py`\n> fails because the dataset file (e.g. `my_data.jsonl` or `dummy_data.jsonl`)\n> cannot be found, you **MUST** inform the user that the dataset file does not\n> exist or cannot be accessed. You **MUST** prompt the user to provide a valid\n> dataset path, and stop tool execution immediately to wait for their response.\n> Do **NOT** retry or loop, do **NOT** invent a specific cost number, and do\n> **NOT** prompt for job submission approval before receiving a valid dataset\n> from the user.\n\n-   **Prompt for Confirmation:** Present the recommended hyperparameter\n    configuration and estimated cost (with the concrete dollar figure calculated\n    above) to the user and ask for their approval before proceeding to job\n    submission. Make sure to note that the estimated cost is just an estimate\n    and can vary from actual billing costs.\n\n## Phase 3: Tuning Job Execution {#phase-3-tuning-job-execution}\n\n**CRITICAL Pre-Flight Check (GCS Verification):** Before you propose a\nconfirmation prompt or submit any tuning job, you **MUST** verify that the\nspecified training dataset GCS URI (e.g. `gs:\u002F\u002Fdummy_bucket\u002Fdataset.jsonl` or\n`gs:\u002F\u002FYOUR_BUCKET\u002F...`) actually exists and is accessible. Run `gcloud storage\nls $DATASET_URI` (or `gsutil ls`).\n\n*   **If the verification fails** (e.g. `BucketNotFound`, `404`, `AccessDenied`,\n    or indicating a dummy\u002Fmissing bucket), you **MUST** inform the user that the\n    GCS bucket or dataset does not exist or cannot be accessed. You **MUST**\n    prompt the user to provide a valid GCS URI for the dataset, and stop tool\n    execution immediately to wait for their response. Do **NOT** propose a\n    confirmation prompt and do **NOT** execute any tuning scripts before\n    receiving a valid dataset URI from the user.\n*   **If the verification succeeds**, proceed to propose the confirmation prompt\n    below.\n\n### For Gemini Models\n\nSubmit the Gemini supervised fine-tuning job using the Python SDK\n(`google.genai` or `vertexai.tuning.sft`):\n\n```python\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(enterprise=True, project=PROJECT, location=LOCATION)\ntuning_job = client.tunings.tune(\n    base_model=BASE_MODEL,  # e.g. \"gemini-2.5-flash\"\n    training_dataset=types.TuningDataset(gcs_uri=TRAIN_DATASET_URI),\n    config=types.CreateTuningJobConfig(\n        epoch_count=EPOCHS,  # e.g. 3\n        learning_rate_multiplier=LEARNING_RATE_MULTIPLIER,  # e.g. 1.0\n        validation_dataset=(\n            types.TuningValidationDataset(gcs_uri=VAL_DATASET_URI)\n            if VAL_DATASET_URI\n            else None\n        ),\n    ),\n)\nprint(\"Tuning Job Resource Name:\", tuning_job.name)\n```\n\nAlternatively using `vertexai.tuning.sft`:\n\n```python\nimport vertexai\nfrom vertexai.tuning import sft\n\nvertexai.init(project=PROJECT, location=LOCATION)\njob = sft.train(\n    source_model=BASE_MODEL,\n    train_dataset=TRAIN_DATASET_URI,\n    validation_dataset=VAL_DATASET_URI,\n    epochs=EPOCHS,\n    learning_rate_multiplier=LEARNING_RATE_MULTIPLIER,\n)\nprint(\"Tuning Job Resource Name:\", job.resource_name)\n```\n\nExecute the Python script via `python3` (inline or written to\n`\u002Ftmp\u002Fsubmit_gemini_tuning.py`). Report the returned operation name or trackable\nresource identifier, and do NOT wait for the terminal state.\n\n### For Open Models\n\nSubmit the open model tuning job using `scripts\u002Ftune_open_model.py` or the\nPython SDK. Identify the model id using available models documentation\nat\n[documentation](https:\u002F\u002Fdocs.cloud.google.com\u002Fgemini-enterprise-agent-platform\u002Fmodels\u002Fopen-model-tuning#supported-models).\n\n\n`--base_model` takes a publisher model **resource name**\n(`{publisher}\u002F{model_id}@{version_id}`), not the display name shown in the\ncatalog. See \"Model Resource Name Format\" in `references\u002Fmodels.md` for the\nformat, verified examples, and how to look up a name you do not have.\n\nUsing `scripts\u002Ftune_open_model.py`:\n\n```bash\npython3 scripts\u002Ftune_open_model.py \\\n    --project YOUR_PROJECT \\\n    --location global \\\n    --base_model BASE_MODEL_ID \\\n    --train_dataset gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Ftrain.jsonl \\\n    --output_uri gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput \\\n    --epochs EPOCHS \\\n    --learning_rate LR \\\n    --tuning_mode MODE\n```\n\n*(Run `scripts\u002Ftune_open_model.py`, which maps `--tuning_mode` (`FULL` \u002F\n`PEFT_ADAPTER`) to `types.TuningMode`, sets adapter sizes and labels, and passes\n`output_uri` into `CreateTuningJobConfig(output_uri=...)`.)*\n\nThis script is open model only, and `--location` falls back to `global` if\nomitted. Always pass the location the user confirmed in section 0.2 explicitly,\nso it is visible in the command string you present for approval. If job\nsubmission fails (e.g., due to a missing or invalid parameter), report the error\nverbatim and present a fresh confirmation prompt showing the updated command\nbefore resubmitting.\n\n#### Distillation\n\nAdd `--method DISTILLATION` and `--teacher_model`. `--base_model` is the\nstudent. Use only the teacher and student models listed under \"Distillation\" in\n`references\u002Fmodels.md`.\n\n```bash\npython3 scripts\u002Ftune_open_model.py \\\n    --project YOUR_PROJECT \\\n    --location global \\\n    --method DISTILLATION \\\n    --teacher_model TEACHER_MODEL_ID \\\n    --base_model STUDENT_MODEL_ID \\\n    --train_dataset gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Fprompts.jsonl \\\n    --output_uri gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput \\\n    --epochs EPOCHS \\\n    --learning_rate LR \\\n    --tuning_mode MODE\n```\n\n-   The training dataset can contain prompts only; the teacher writes the\n    responses. A validation dataset must include responses.\n-   Teacher calls draw on the project's quota for the teacher model.\n-   The job writes the teacher-labeled data to\n    `distillation_labelled_dataset.jsonl` in the output directory.\n\n> [!WARNING] **`--output_uri` \u002F `output_uri` is required for open models.** The\n> Python SDK declares it as `output_uri: Optional[str] = None`, but the tuning\n> backend rejects open model jobs that omit it with `INVALID_ARGUMENT: The\n> output_uri field is required for this model.` Treat the SDK's \"optional\"\n> signature as wrong here and always pass a GCS destination (`output_uri`) both\n> in CLI flags and in `CreateTuningJobConfig(output_uri=...)`.\n\nBecause the flag is mandatory, you must establish where the tuned model is\nwritten before you can submit. **Never invent a bucket name, derive one from the\nproject number, or run `gcloud storage buckets create` unprompted.** Creating a\nbucket is a mutating action and is subject to the Tier M confirmation policy\nbelow.\n\n-   **The user named a bucket or URI** → use it EXACTLY as specified by the user\n    (verbatim), appending a unique per-job directory as in section 1.2.\n    **CRITICAL:** NEVER alter, prefix, or prepend the project number or anything\n    else to a user-specified bucket name! Even if `gcloud storage buckets list`\n    shows an existing bucket with a project-prefixed name (e.g.\n    `gs:\u002F\u002FPROJECT-mybucket` when the user asked for `gs:\u002F\u002Fmybucket`), you MUST\n    use the user's exact bucket name `gs:\u002F\u002Fmybucket` verbatim. Never silently\n    substitute an existing bucket.\n-   **A bucket was already used for the dataset upload in section 1.2** →\n    propose reusing it for the output and ask the user to confirm.\n-   **Neither or user states they have no bucket** → Check existing buckets in\n    the project (`gcloud storage buckets list --project=PROJECT`) or offer to\n    create a dedicated bucket. When proposing a bucket to create, ensure the\n    bucket name is unique by including a unique suffix or timestamp (e.g.\n    `gs:\u002F\u002FPROJECT-tuning-$(date +%s)` or\n    `gs:\u002F\u002FPROJECT-tuning-artifacts-\u003Ctimestamp>` in LOCATION) to prevent HTTP 409\n    collisions with previously created buckets. Propose the destination bucket\n    in your configuration dry-run preview and ask the user for confirmation\n    before proceeding.\n\n> [!IMPORTANT] **Interactive Confirmation Required (Tier M):** Before proceeding\n> with job submission, you **MUST** present the proposed command string showing\n> all literal flags (including `--output_uri` for open models and estimated\n> cost) in a confirmation prompt to the user with 'Yes' and 'No' options. Do NOT\n> execute the tuning job submission via `run_command` in the same turn as\n> presenting the confirmation prompt; yield immediately for the user's reply.\n\n## Phase 4: Monitoring {#phase-4-monitoring}\n\nMonitor the job via the Cloud Console link provided in the script output.\n`--location` is required and must be the same location you submitted with: an\nopen model job submitted on `global` is polled with `--location global`, even\nthough the work runs in a real region behind the scenes.\n\nAdditionally, ask the user if they want you to monitor the job status for them\nin the background. If they agree, execute `scripts\u002Fmonitor_tuning_job.py` as a\nbackground task to periodically poll the job status and notify the user to show\nthe status. If the user declines, leave it completely to the user to check on\nthe status.\n\n## Phase 5: Model Deployment {#phase-5-model-deployment}\n\nOnce the tuning job is `SUCCEEDED`, deploy the tuned checkpoint by registering\nit in Model Registry with the base model's serving container, then deploying\nthat registered model to a new endpoint. **CRITICAL:** If the tuning job is\nstill pending\u002Frunning (`tuned_model_name` is `None` or not yet generated),\n**NEVER deploy the untuned base model (e.g., `google\u002Fgemma3@...`) as a\nsubstitute!** Instead, explicitly inform the user that deployment of the tuned\nmodel cannot proceed until the tuning job finishes (`JOB_STATE_SUCCEEDED`) and\ngenerates the tuned model artifact.\n\nDeployment requires a real region — `global` is not valid here. If the job ran\non `global`, read the region out of the tuned model's resource name\n(`projects\u002F...\u002Flocations\u002F\u003CREGION>\u002Fmodels\u002F...`) and deploy there; do not guess.\n\nIf a deploy step is rejected for quota, report the API's error verbatim.\n\n```bash\n#!\u002Fbin\u002Fbash\n# Deploy a tuned checkpoint by registering it in Model Registry with the base\n# model's serving container, then deploying to a new endpoint. All four gcloud\n# calls hit aiplatform.googleapis.com.\nset -euo pipefail\nPROJECT_ID=\"YOUR_PROJECT\"\nLOCATION_ID=\"YOUR_LOCATION\"          # e.g. us-central1; NOT global.\nBASE_MODEL=\"\u003CPUBLISHER>\u002F\u003CFAMILY>@\u003CVERSION-ID>\"   # the base model you tuned; e.g. google\u002Fgemma3@gemma-3-4b-it\nARTIFACTS=\"gs:\u002F\u002FYOUR_BUCKET\u002Ftuning_agent_job_\u003Cdatetime>\u002Foutput\u002Fpostprocess\u002Fnode-0\u002Fcheckpoints\u002Ffinal\"\nDISPLAY_NAME=\"my-tuned-model\"\n# Explicit IDs, so no step can pick up an older model or endpoint that shares\n# the display name.\nRUN_ID=\"tuned-$(date +%Y%m%d-%H%M%S)\"\n\n# 1. List the base model's verified deployment configs and pick the row that\n#    fits the user-approved cost\u002Fperf. CONFIG_INDEX is its 0-based table row.\ngcloud ai model-garden models list-deployment-config \\\n    --model=\"$BASE_MODEL\" \\\n    --project=\"$PROJECT_ID\"\nCONFIG_INDEX=\"\u003Cpick from step 1>\"\n\n# Turn that row into gcloud flags files. The table shows only the image, but\n# the container also needs the row's args, env, ports and routes; its\n# --model\u002F--model-path arg points at the base weights and is repointed at\n# $ARTIFACTS.\ngcloud ai model-garden models list-deployment-config \\\n    --model=\"$BASE_MODEL\" \\\n    --project=\"$PROJECT_ID\" \\\n    --format=json | python3 -c '\nimport json, sys\nidx, artifacts = int(sys.argv[1]), sys.argv[2]\ncfg = json.load(sys.stdin)[idx]\nspec = dict(cfg[\"containerSpec\"])\nargs, repointed = [], 0\nfor a in spec.pop(\"args\", []):\n    flag = a.split(\"=\", 1)[0]\n    if flag in (\"--model\", \"--model-path\"):\n        a, repointed = flag + \"=\" + artifacts, repointed + 1\n    args.append(a)\nif repointed != 1:\n    sys.exit(\"expected exactly one --model\u002F--model-path arg, found %d\" % repointed)\nupload = {\"--container-image-uri\": spec.pop(\"imageUri\"), \"--container-args\": args}\nif \"command\" in spec:\n    upload[\"--container-command\"] = spec.pop(\"command\")\nif \"env\" in spec:\n    upload[\"--container-env-vars\"] = {e[\"name\"]: e[\"value\"] for e in spec.pop(\"env\")}\nif \"ports\" in spec:\n    upload[\"--container-ports\"] = [str(p[\"containerPort\"]) for p in spec.pop(\"ports\")]\nfor key, flag in ((\"predictRoute\", \"--container-predict-route\"),\n                  (\"healthRoute\", \"--container-health-route\"),\n                  (\"sharedMemorySizeMb\", \"--container-shared-memory-size-mb\")):\n    if key in spec:\n        upload[flag] = str(spec.pop(key))\nif \"deploymentTimeout\" in spec:  # e.g. \"7200s\"\n    upload[\"--container-deployment-timeout-seconds\"] = str(\n        int(float(spec.pop(\"deploymentTimeout\").rstrip(\"s\"))))\nif spec:\n    sys.exit(\"unhandled containerSpec fields: %s\" % sorted(spec))\nm = cfg[\"dedicatedResources\"][\"machineSpec\"]\ndeploy = {\"--machine-type\": m[\"machineType\"]}\nif \"acceleratorType\" in m:\n    deploy[\"--accelerator\"] = {\n        \"type\": m[\"acceleratorType\"].lower().replace(\"_\", \"-\"),\n        \"count\": str(m.get(\"acceleratorCount\", 1))}\njson.dump(upload, open(\"\u002Ftmp\u002Fupload_flags.yaml\", \"w\"), indent=1)\njson.dump(deploy, open(\"\u002Ftmp\u002Fdeploy_flags.yaml\", \"w\"), indent=1)\n' \"$CONFIG_INDEX\" \"$ARTIFACTS\"\ncat \u002Ftmp\u002Fupload_flags.yaml \u002Ftmp\u002Fdeploy_flags.yaml\n\n# 2. Register the tuned checkpoint in Model Registry with the base model's\n#    serving container. Blocks until the upload finishes.\ngcloud ai models upload \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --model-id=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\" \\\n    --artifact-uri=\"$ARTIFACTS\" \\\n    --flags-file=\u002Ftmp\u002Fupload_flags.yaml\n\n# 3. Create a dedicated endpoint.\ngcloud ai endpoints create \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --endpoint-id=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\"\n\n# 4. Deploy the registered model to the endpoint.\ngcloud ai endpoints deploy-model \"$RUN_ID\" \\\n    --project=\"$PROJECT_ID\" \\\n    --region=\"$LOCATION_ID\" \\\n    --model=\"$RUN_ID\" \\\n    --display-name=\"$DISPLAY_NAME\" \\\n    --flags-file=\u002Ftmp\u002Fdeploy_flags.yaml\n```\n\n`set -e` stops the sequence at the first failed step. Step 4 blocks until the\nmodel is serving, which can take 20 minutes or more; its progress output prints\nthe operation name, `projects\u002F\u003CPROJECT>\u002Flocations\u002F\u003CREGION>\u002Foperations\u002F\u003COP_ID>`.\nPass that whole name to `gcloud ai operations describe \u003COPERATION_NAME>` for\nstatus checks from another shell: a bare `\u003COP_ID>` works only when gcloud has a\nproject.\n\n> [!IMPORTANT] **Interactive Confirmation Required (Tier M):** Before proceeding\n> with deployment, you **MUST** present the three write commands (steps 2-4)\n> with all literal values filled in, plus the generated flags files (container\n> image, args, machine type, accelerator config), in a confirmation prompt to\n> the user with 'Yes' and 'No' options. Do NOT execute the deployment commands\n> via `run_command` in the same turn as presenting the confirmation prompt;\n> yield immediately for the user's reply.\n\nRefer to [Models Catalog](references\u002Fmodels.md) for hardware recommendations for\nspecific open models.\n\n## Resources\n\n-   [Data Preparation Guide](references\u002Fdata_prep.md)\n-   [Models Catalog](references\u002Fmodels.md)\n-   [Tuning Guide](references\u002Ftuning_guide.md)\n-   `scripts\u002Fprepare_dataset.py`: Data conversion & validation.\n-   `scripts\u002Ftune_open_model.py`: Open model tuning job submission.\n",{"name":5,"metadata":52,"description":7},{"version":53,"category":54},"1.0.2","AiAndMachineLearning","main","071c4f7eba880767a647ce778650db8dbf7e16fc","skills\u002Fcloud\u002Fagent-platform-tuning",2005,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills","https:\u002F\u002Fgithub.com\u002Fgoogle","https:\u002F\u002Fopensource.google\u002F","Google ❤️ Open Source","2026-11-14 01:51:35",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add google\u002Fskills --skill agent-platform-tuning",{"tool":70,"command":71},"bunx","bunx skills add google\u002Fskills --skill agent-platform-tuning",{"tool":73,"command":74},"pnpm","pnpm dlx skills add google\u002Fskills --skill agent-platform-tuning","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002FSKILL.md",true,[78,84,88,92,96,100,104,110,114,118,122,126],{"path":79,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":82,"sourceHtmlUrl":83},"references\u002Fattribution.md","REFERENCE","参考文档",1937,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Fattribution.md",{"path":85,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":86,"sourceHtmlUrl":87},"references\u002Fdata_prep.md",4107,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Fdata_prep.md",{"path":89,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":90,"sourceHtmlUrl":91},"references\u002Fhf_datasets.md",5442,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Fhf_datasets.md",{"path":93,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":94,"sourceHtmlUrl":95},"references\u002Fmodels.md",10076,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Fmodels.md",{"path":97,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":98,"sourceHtmlUrl":99},"references\u002Frequirements.txt",100,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Frequirements.txt",{"path":101,"resourceType":80,"resourceTypeDesc":81,"sizeBytes":102,"sourceHtmlUrl":103},"references\u002Ftuning_guide.md",2315,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Freferences\u002Ftuning_guide.md",{"path":105,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":108,"sourceHtmlUrl":109},"scripts\u002Fcalculate_cost.py","SCRIPT","脚本",13427,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Fcalculate_cost.py",{"path":111,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":112,"sourceHtmlUrl":113},"scripts\u002Fcancel_tuning_job.py",1130,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Fcancel_tuning_job.py",{"path":115,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":116,"sourceHtmlUrl":117},"scripts\u002Flist_models.py",1052,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Flist_models.py",{"path":119,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":120,"sourceHtmlUrl":121},"scripts\u002Fmonitor_tuning_job.py",2308,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Fmonitor_tuning_job.py",{"path":123,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":124,"sourceHtmlUrl":125},"scripts\u002Fprepare_dataset.py",11976,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Fprepare_dataset.py",{"path":127,"resourceType":106,"resourceTypeDesc":107,"sizeBytes":128,"sourceHtmlUrl":129},"scripts\u002Ftune_open_model.py",4075,"https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fagent-platform-tuning\u002Fscripts\u002Ftune_open_model.py",[131,167,195,224,251,274],{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":135,"categories":136,"keywords":143,"publishedAt":47},"01M6998S2EZKD3GEJ7F943RZTE","sign-in-with-google-web","Implement, configure, and secure Sign In With Google (SiwG) using Google Identity Services (GIS \u002F `https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fclient`) across web architectures. Use when creating Google sign-in buttons, implementing Google One Tap with FedCM, integrating GIS in React\u002FNext.js\u002FAngular\u002FHTML, verifying ID tokens on backend runtimes (Python\u002FNode.js\u002FGo\u002FJava), enforcing Google Workspace domain restrictions (`hd`), securing client-side ID tokens via WebCrypto nonces or encrypted IndexedDB, embedding in cross-origin iframes via the Intermediate Iframe API (`https:\u002F\u002Faccounts.google.com\u002Fgsi\u002Fintermediate`), configuring Content Security Policy (CSP), COOP, and Permissions Policy (`identity-credentials-get`) headers, or handling sign-out and revocation. Don't use for legacy Google Sign-In (`gapi.auth2`), Google Cloud IAM service accounts, or Android\u002FiOS native Credential Manager.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fsign-in-with-google-web\u002FSKILL.md",[137,140],{"categoryNo":138,"categoryName":139,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",[9,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166],"sign in with google web","sign","with","web","android","go","security","google workspace","google cloud","implement","policy","python","implementing","react","iam","permissions","buttons","secure","manager","identity","architectures","domain","runtimes",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":180,"publishedAt":47},"01M6998PHPDCAD684VW5WRG83D","dpop-adoption","Implement and debug OAuth 2.0 DPoP (RFC 9449) refresh token sender-constraining for WebCrypto, Node.js ES6, and browser runtimes integrating with Google's OAuth platform. Use when configuring non-extractable asymmetric key pairs (P-256), generating DPoP Proof JWTs for authorization code exchange and token refresh, or handling 400 use_dpop_nonce challenge retry loops at oauth2.googleapis.com\u002Ftoken. Don't use for unconstrained OAuth 2.0 flows (where refresh tokens are not bound to a client key pair), or for Google Cloud IAM \u002F service account authentication.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fidentity\u002Fdpop-adoption\u002FSKILL.md",[173,174,175],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":176,"categoryName":177,"taxonomyType":178,"taxonomyTypeDesc":179},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[181,182,9,152,183,184,185,186,187,188,158,189,190,191,192,193,194,163,166,26,30],"dpop adoption","adoption","es6","retry","authentication","flows","challenge","code","browser","loops","at","node","token","pair",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":199,"categories":200,"keywords":207,"publishedAt":47},"01M6998M72TC73EEAQH4XC7EXW","retrieving-developer-knowledge","Searches, retrieves, and synthesizes official Google developer documentation across Google Cloud, AI\u002FGemini, Android, Chrome, Web, Flutter, Go, Firebase, and other Google developer platforms. Integrates with the Developer Knowledge MCP server (search_documents, get_documents, answer_query) or the Developer Knowledge REST API fallback. Use when searching for gcloud CLI commands, API syntax, IAM permissions, official documentation, architectural comparisons, or product choice overviews. Don't use for local filesystem lookups or non-Google documentation.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fretrieving-developer-knowledge\u002FSKILL.md",[201,202,205,206],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":203,"categoryName":204,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":176,"categoryName":177,"taxonomyType":178,"taxonomyTypeDesc":179},[208,209,210,211,152,212,213,214,215,216,217,218,219,30,220,221,222,158,223,159],"retrieving developer knowledge","knowledge","developer","MCP","mcp server","official","developer documentation","get","search","local","choice","documents","query","api","across","documentation",{"skillNo":225,"name":226,"nameZh":6,"description":227,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":228,"categories":229,"keywords":230,"publishedAt":47},"01M6998J2CZEXS9BES9S3WWFKC","migrate-to-rust","Clones, analyzes, plans, and migrates legacy codebases (C++, Fortran, Java, Python, C, Shell) from a GitHub URL or local directory to a modern, high-performance, deterministic, 100% Safe Rust architecture, tracking live telemetry across every task bucket, test failure\u002Ffix cycle, and Profile-Guided Optimization (PGO) stage, and producing a first-person AI storytelling report and interactive HTML infographic. Use when migrating a GitHub repository or local codebase to Rust, porting legacy C++, Fortran, Python, or Java workloads to memory-safe Rust, designing Struct-of-Arrays (SoA) or SIMD Rust architectures, or optimizing Rust ports with LLVM PGO. Don't use for unrelated non-Rust refactoring or single-line syntax questions.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Fmigrate-to-rust\u002FSKILL.md",[],[231,232,233,234,235,236,237,238,155,239,240,241,242,243,244,217,245,246,247,30,248,249,222,250],"migrate to rust","rust","migrate","github","memory","performance","directory","safe","ports","llvm","soa","cycle","porting","fix","arrays","infographic","high-performance","URL","architecture","unrelated",{"skillNo":252,"name":253,"nameZh":6,"description":254,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":255,"categories":256,"keywords":260,"publishedAt":47},"01M6998GCC3K29WZ82KXMB9E02","finding-google-skills","Google platform decision and setup guidance, loaded on demand from Google's skill catalog. Use when a developer is choosing or setting up part of their stack, such as where to run a service, a database, storage, messaging, authentication, analytics, ads, or AI model serving, and a Google product is a reasonable candidate - whether or not a vendor is named - or when a request names a Google product or API. Brings in the matching Google skill so the answer can weigh Google options, their trade-offs, and when they are not the right fit. Skip when the stack is already settled on another provider and no Google product is named, or the task involves no platform choice.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdevelopers\u002Ffinding-google-skills\u002FSKILL.md",[257,258,259],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":176,"categoryName":177,"taxonomyType":178,"taxonomyTypeDesc":179},[9,261,262,263,264,26,41,265,266,267,268,185,269,270,271,218,39,272,30,273,221],"finding google skills","storage","as","messaging","trade-offs","provider","answer","fit","catalog","up","decision","database","candidate",{"skillNo":275,"name":276,"nameZh":6,"description":277,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":278,"categories":279,"keywords":282,"publishedAt":47},"01M6998DWNHTH0BS6W1CY8ZXMP","workload-manager-basics","Use this skill to manage Google Cloud Workload Manager evaluations, rules, scanned resources, and validation results by using public client libraries and the REST API. Use when you need to inspect workload best-practice rules, create and run evaluations for Google Cloud general best practices, SAP, SQL Server, or custom organizational rules, review violations, export results to BigQuery, or automate Workload Manager through client libraries because no service-specific public CLI or MCP server is available. Don't use for general Google Compute Engine instance management, VPC configuration, or standard IAM auditing.","https:\u002F\u002Fgithub.com\u002Fgoogle\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fcloud\u002Fworkload-manager-basics\u002FSKILL.md",[280,281],{"categoryNo":141,"categoryName":142,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[283,284,162,285,9,211,286,152,212,287,288,289,290,30,34,221,158,291,292,293,294],"workload manager basics","workload","basics","cli","review","vpc","resources","inspect","create","libraries","server","run"]