{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ur8xi4C7S06n"
      },
      "outputs": [],
      "source": [
        "# Copyright 2025 Google LLC\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JAPoU8Sm5E6e"
      },
      "source": [
        "# Rubric evaluation - Multimodal and Custom metric for text quality\n",
        "\n",
        " <table align=\"left\">\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://www.gstatic.com/pantheon/images/bigquery/welcome_page/colab-logo.svg\" alt=\"Google Colaboratory logo\"><br> Open in Colab\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/colab/import/https:%2F%2Fraw.githubusercontent.com%2FGoogleCloudPlatform%2Fgenerative-ai%2Fmain%2Fgemini%2Fevaluation%2Fevaltask_approach%2Fmultimodal_text_quality_rubric_evaluation.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://lh3.googleusercontent.com/JmcxdQi-qOpctIvWKgPtrzZdJJK-J3sWE1RsfjZNwshCFgE_9fULcNpuXYTilIR2hjwN\" alt=\"Google Cloud Colab Enterprise logo\"><br> Open in Colab Enterprise\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/GoogleCloudPlatform/generative-ai/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\">\n",
        "      <img src=\"https://www.gstatic.com/images/branding/gcpiconscolors/vertexai/v1/32px.svg\" alt=\"Vertex AI logo\"><br> Open in Vertex AI Workbench\n",
        "    </a>\n",
        "  </td>\n",
        "  <td style=\"text-align: center\">\n",
        "    <a href=\"https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\">\n",
        "      <img width=\"32px\" src=\"https://raw.githubusercontent.com/primer/octicons/refs/heads/main/icons/mark-github-24.svg\" alt=\"GitHub logo\"><br> View on GitHub\n",
        "    </a>\n",
        "  </td>\n",
        "</table>\n",
        "\n",
        "<div style=\"clear: both;\"></div>\n",
        "\n",
        "<b>Share to:</b>\n",
        "\n",
        "<a href=\"https://www.linkedin.com/sharing/share-offsite/?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/8/81/LinkedIn_icon.svg\" alt=\"LinkedIn logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://bsky.app/intent/compose?text=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/7/7a/Bluesky_Logo.svg\" alt=\"Bluesky logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://twitter.com/intent/tweet?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/5a/X_icon_2.svg\" alt=\"X logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://reddit.com/submit?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://redditinc.com/hubfs/Reddit%20Inc/Brand/Reddit_Logo.png\" alt=\"Reddit logo\">\n",
        "</a>\n",
        "\n",
        "<a href=\"https://www.facebook.com/sharer/sharer.php?u=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaltask_approach/multimodal_text_quality_rubric_evaluation.ipynb\" target=\"_blank\">\n",
        "  <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg\" alt=\"Facebook logo\">\n",
        "</a>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "84f0f73a0f76"
      },
      "source": [
        "| Author(s) |\n",
        "| --- |\n",
        "| [Ivan Nardini](https://github.com/inardini), [Naveksha Sood](https://github.com/navekshasood)|"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8AkOkTWDA29g"
      },
      "source": [
        "## Overview\n",
        "\n",
        "This notebook showcases how to perform rubric-based evaluation using Vertex AIGenAI Evaluation service. You will learn how to use both predefined metrics for multimodal tasks and how to create your own custom rubric-based metrics from scratch.\n",
        "\n",
        "### Why Use Rubric-Based Evaluation?\n",
        "\n",
        "When evaluating Large Language Models (LLMs), simple metrics like accuracy often don't capture the full picture. How do you measure nuance, creativity, adherence to a specific style, or the quality of a summary?\n",
        "\n",
        "**Rubric-based evaluation** solves this by using a powerful technique: **using an LLM to evaluate another LLM.** Instead of relying on rigid, predefined criteria, we generate a dynamic \"rubric\" (a set of questions) tailored to the specific prompt. An \"auto-rater\" model then assesses the LLM's response against this rubric.\n",
        "\n",
        "This approach is powerful because it allows you to:\n",
        "\n",
        "*   **Evaluate Complex, Subjective Tasks:** Go beyond simple right/wrong answers to assess quality, style, and safety.\n",
        "*   **Align Evaluation with Your Goals:** Create custom rubrics that measure what truly matters for your specific use case.\n",
        "*   **Scale Your Evaluations:** Automate the process of nuanced evaluation, saving countless hours of manual review.\n",
        "\n",
        "### Steps in Rubric-Based Evaluation\n",
        "\n",
        "1. **Rubric Generation**: An LLM generates a set of evaluation questions (the rubric) based on the inference prompt.\n",
        "2. **Rubric Revision** [Optional but Recommended]: You inspect and revise the generated questions to ensure they align perfectly with your requirements.\n",
        "3. **Rubric Critiquing**: An autorater LLM judges the model's response against the rubric. This can be done **pointwise** (scoring a single response) or **pairwise** (comparing two different responses).\n",
        "\n",
        "In this tutorial, you'll learn how to use both predefined and custom rubrics to evaluate model responses for multimodal and text-based tasks."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BMVCWDXQiAvt"
      },
      "source": [
        "## Getting Started\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BMHhBIAaCSLv"
      },
      "source": [
        "### Install Google Vertex AI SDK and other required packages"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "H86tTuJB2G-t"
      },
      "outputs": [],
      "source": [
        "%pip install --upgrade --quiet \"google-cloud-aiplatform[evaluation]\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TnWgrAawCOWY"
      },
      "source": [
        "### Authenticate your notebook environment (Colab only)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "llaaq7fX2ApN"
      },
      "outputs": [],
      "source": [
        "import sys\n",
        "\n",
        "if \"google.colab\" in sys.modules:\n",
        "    from google.colab import auth\n",
        "\n",
        "    auth.authenticate_user()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fQUNOVe3DLmU"
      },
      "source": [
        "### Set Google Cloud project information\n",
        "To get started using Vertex AI, you must have an existing Google Cloud project and enable the Vertex AI API.\n",
        "\n",
        "Learn more about setting up a project and a development environment."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8yvd828X5ae8"
      },
      "outputs": [],
      "source": [
        "import vertexai\n",
        "\n",
        "PROJECT_ID = \"[your-project-id]\"  # @param {type: \"string\", placeholder: \"[your-project-id]\", isTemplate: true}\n",
        "LOCATION = \"us-central1\"  # @param {type:\"string\"}\n",
        "\n",
        "BUCKET_NAME = \"[your-bucket-name]\"  # @param {type: \"string\", placeholder: \"[your-bucket-name]\", isTemplate: true}\n",
        "BUCKET_URI = f\"gs://{BUCKET_NAME}\"\n",
        "\n",
        "!gsutil mb -l {LOCATION} {BUCKET_URI}\n",
        "\n",
        "vertexai.init(project=PROJECT_ID, location=LOCATION)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cCcrGIzHDXEb"
      },
      "source": [
        "### Import libraries\n",
        "\n",
        "Let's import the necessary libraries from the Vertex AI SDK and other packages we'll use throughout the notebook."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nGtD6oEy5h0G"
      },
      "outputs": [],
      "source": [
        "import pandas as pd\n",
        "from vertexai.generative_models import GenerativeModel\n",
        "from vertexai.preview.evaluation import (\n",
        "    CustomOutputConfig,\n",
        "    EvalTask,\n",
        "    PointwiseMetric,\n",
        "    PredefinedRubricMetrics,\n",
        "    RubricBasedMetric,\n",
        "    RubricGenerationConfig,\n",
        "    notebook_utils,\n",
        "    utils,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qVenG4qeNXCj"
      },
      "source": [
        "### Helpers\n",
        "\n",
        "This helper function provides a clean way to display the contents of a Pandas DataFrame, which we will use to inspect our datasets and results."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EkcuPek0Nal0"
      },
      "outputs": [],
      "source": [
        "def display_df(\n",
        "    df: pd.DataFrame, num_rows: int | None = 5, line_length: int = 80\n",
        ") -> None:\n",
        "    \"\"\"\n",
        "    Displays DataFrame rows cleanly, ensuring full text visibility.\n",
        "    \"\"\"\n",
        "    # Explicitly select the data slice to avoid ambiguity.\n",
        "    data_to_display = df if num_rows is None else df.head(num_rows)\n",
        "\n",
        "    if data_to_display.empty:\n",
        "        print(\"The DataFrame is empty.\")\n",
        "        return\n",
        "\n",
        "    # A simple loop is better than a complex, nested structure.\n",
        "    for _, row in data_to_display.iterrows():\n",
        "        print(\"-\" * line_length)\n",
        "        for col, value in row.items():\n",
        "            # Readability counts: Clearly label the column and show the value.\n",
        "            print(f\"{col}: {value}\")\n",
        "    print(\"-\" * line_length)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "olm65I4C91uQ"
      },
      "source": [
        "## Rubric based Multimodal Understanding\n",
        "\n",
        "In this section, we'll evaluate a use case where an AI model acts as an insurance agent. The model receives an image of a damaged car and a prompt asking it to assess the damage, severity, and estimated repair cost.\n",
        "\n",
        "Since the Vertex AI Gen AI Evaluation doesn't yet support direct multimodal *inference* within the `EvalTask`, we'll start with pre-generated responses. \n",
        "\n",
        "You would typically generate these responses by calling a multimodal model like Gemini."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "W7ZbddnMGUyk"
      },
      "source": [
        "### Create a multimodal dataset\n",
        "\n",
        "The dataset contains:\n",
        "1. **prompt**: The instruction given to the language model.\n",
        "2. **image**: A URI pointing to an image file in Google Cloud Storage.\n",
        "3. **response**: The text generated by our candidate model.\n",
        "4. **baseline_model_response**: The text generated by our baseline model for comparison."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "51zYJElW6r-F"
      },
      "outputs": [],
      "source": [
        "image = [\n",
        "    '{\"contents\": [{\"parts\": [{\"file_data\": {\"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/evaluation/use_cases/car_assessment/bumper.jpg\"}}]}]}',\n",
        "    '{\"contents\": [{\"parts\": [{\"file_data\": {\"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/evaluation/use_cases/car_assessment/engine_compartment.jpg\"}}]}]}',\n",
        "    '{\"contents\": [{\"parts\": [{\"file_data\": {\"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/evaluation/use_cases/car_assessment/hood.jpg\"}}]}]}',\n",
        "    '{\"contents\": [{\"parts\": [{\"file_data\": {\"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/evaluation/use_cases/car_assessment/lateral.jpg\"}}]}]}',\n",
        "    '{\"contents\": [{\"parts\": [{\"file_data\": {\"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/evaluation/use_cases/car_assessment/windshield.jpg\"}}]}]}',\n",
        "]\n",
        "\n",
        "prompt = [\n",
        "    \"You are an insurance agent specializing in car accident assessments. You will be provided with a conversation about a car accident and an image of the damaged car. Your task is to analyze the image and identify the primary type of damage visible. Use the conversation for context, but prioritize the visual evidence from the image. Provide your assessment including the primary damage type, the severity of the damage, and a rough estimation of the repair cost.\",\n",
        "] * len(image)\n",
        "\n",
        "response = [\n",
        "    \"\"\"**Primary Damage:** Bumper.\n",
        "            **Severity:** Moderate. The rear bumper is visibly dented, scratched, and partially detached from the vehicle's body.\n",
        "            **Estimated Cost:** The repair cost is estimated to be between $500 and $1,500. This includes the cost of either repairing the existing bumper or replacing it, as well as painting to match the vehicle's color.\"\"\",\n",
        "    \"\"\"**Primary Damage:** Engine Compartment.\n",
        "            **Severity:** Severe. The image shows a catastrophic front-end collision where the hood is completely buckled and the underlying engine components are exposed and likely damaged.\n",
        "            **Estimated Cost:** The repair costs are expected to be substantial, likely ranging from $5,000 to over $10,000. Given the severity, the vehicle may be declared a total loss by the insurance company.\"\"\",\n",
        "    \"\"\"**Primary Damage:** Hood.\n",
        "            **Severity:** Moderate to Severe. The hood is significantly crumpled and bent upwards, indicating a forceful front-end impact.\n",
        "            **Estimated Cost:** The cost for replacing and painting the hood is estimated to be between $1,000 and $2,500. This does not include potential underlying damage to the radiator, grille, or engine compartment which would increase the cost.\"\"\",\n",
        "    \"\"\"**Primary Damage:** Lateral.\n",
        "            **Severity:** Severe. There is extensive damage along the driver's side of the vehicle, with both the front and rear doors significantly crushed inwards.\n",
        "            **Estimated Cost:** The estimated repair cost is between $4,000 and $8,000. This type of damage often involves replacing multiple door panels, repainting, and potentially complex and costly repairs to the vehicle's frame and B-pillar.\"\"\",\n",
        "    \"\"\"**Primary Damage:** Windshield.\n",
        "            **Severity:** Minor to Moderate. A large crack is visible running across the windshield.\n",
        "            **Estimated Cost:** The replacement cost is estimated to be between $400 and $1,200. The final price will depend on the make and model of the vehicle and if it is equipped with Advanced Driver-Assistance Systems (ADAS) that require recalibration after the windshield is replaced.\"\"\",\n",
        "]\n",
        "\n",
        "baseline_response = [\n",
        "    \"\"\"**Damage Type:** Bumper\n",
        "            **Severity:** Moderate\n",
        "            **Estimated Cost:** $600 - $1,800. The bumper is visibly damaged and will likely need replacement and painting.\"\"\",\n",
        "    \"\"\"**Damage Type:** Engine Compartment\n",
        "            **Severity:** Severe\n",
        "            **Estimated Cost:** Potentially over $10,000. The damage is extensive and appears to affect the engine. This could be a total loss.\"\"\",\n",
        "    \"\"\"**Damage Type:** Hood\n",
        "            **Severity:** Moderate\n",
        "            **Estimated Cost:** $1,200 - $3,000. This includes a new hood, paint, and labor. There may be additional costs for hidden damage.\"\"\",\n",
        "    \"\"\"**Damage Type:** Lateral\n",
        "            **Severity:** Severe\n",
        "            **Estimated Cost:** $4,500 - $9,000. The damage spans two doors and may have affected the vehicle's frame, requiring significant repair work.\"\"\",\n",
        "    \"\"\"**Damage Type:** Windshield\n",
        "            **Severity:** Minor\n",
        "            **Estimated Cost:** $350 - $900. The cost depends on the vehicle model and whether recalibration of safety features is needed.\"\"\",\n",
        "]\n",
        "\n",
        "\n",
        "eval_dataset = pd.DataFrame(\n",
        "    {\n",
        "        \"prompt\": prompt,\n",
        "        \"image\": image,\n",
        "        \"baseline_model_response\": baseline_response,\n",
        "        \"response\": response,\n",
        "    }\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CFHPv7V8IP9n"
      },
      "source": [
        "### **Run a Pairwise Evaluation with a Predefined Metric**\n",
        "\n",
        "Now, we're going to put our two models in a head-to-head competition for each image in our dataset. This is a **pairwise evaluation**, and it's one of the most effective ways to determine which model is truly better for your specific use case.\n",
        "\n",
        "To do this, we'll configure an `EvalTask`. Think of this as the main orchestrator for our evaluation job. It bundles together our dataset, the metrics we want to compute, and the Cloud Storage location for the results.\n",
        "\n",
        "The key ingredient here is the metric we choose: `PredefinedRubricMetrics.Pairwise.MULTIMODAL_UNDERSTANDING`. It's an off-the-shelf metric designed to handle all the heavy lifting of multimodal pairwise evaluation for you.\n",
        "\n",
        "When you run the cell below, the single command `eval_task.evaluate()` kicks off the evaluation process. Here's a peek at what the Vertex AI Evaluation Service does for you under the hood:\n",
        "\n",
        "1.  **Rubric Generation:** For each row in your dataset, the service's autorater model analyzes the prompt and image to generate a custom set of rubric questions tailored to that specific example.\n",
        "2.  **Pairwise Critique:** The autorater then systematically compares your `response` and `baseline_model_response`, answering each rubric question for both models to see how they stack up.\n",
        "3.  **Scoring and Aggregation:** Finally, the service processes the autorater's raw judgments, calculates the final win/loss/tie scores, and organizes all the rich details—including the generated rubrics and the autorater's reasoning—into the structured `eval_result` DataFrame that gets returned to your notebook.\n",
        "\n",
        "Let's run it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rOIbhuakJrpg"
      },
      "outputs": [],
      "source": [
        "eval_task = EvalTask(\n",
        "    dataset=eval_dataset,\n",
        "    metrics=[PredefinedRubricMetrics.Pairwise.MULTIMODAL_UNDERSTANDING],\n",
        "    output_uri_prefix=BUCKET_URI,\n",
        ")\n",
        "\n",
        "eval_result = eval_task.evaluate()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2bRQpn87aMOT"
      },
      "source": [
        "### Understanding eval results for pairwise rubric based metrics\n",
        "\n",
        "The `eval_result` object contains a wealth of information. Let's break down the key columns in the results DataFrame:\n",
        "\n",
        "*   **`rubrics`**: The list of questions automatically generated by the autorater for this specific prompt and image. This gives you insight into *how* the model was evaluated.\n",
        "*   **`description`**: A text description of the image, generated by the autorater model to provide context for its evaluation.\n",
        "*   **`pairwise_rb_multimodal_understanding/pairwise_choice`**: The final verdict of the autorater. It's a human-readable judgment, such as \"Candidate response is slightly better than the baseline response.\"\n",
        "*   **`pairwise_rb_multimodal_understanding/score`**: A numeric score that maps to the `pairwise_choice`. It's on a 5-point scale:\n",
        "    *   `1.0`: Candidate is better.\n",
        "    *   `0.5`: Candidate is slightly better.\n",
        "    *   `0.0`: Both are equal (or a tie).\n",
        "    *   `-0.5`: Baseline is slightly better.\n",
        "    *   `-1.0`: Baseline is better.\n",
        "*   **`.../candidate_rubric_verdict_pairs`**: A breakdown showing how the candidate response scored on each individual rubric question (e.g., `Does the response identify the primary damage?: True`).\n",
        "*   **`.../baseline_rubric_verdict_pairs`**: The same breakdown for the baseline model.\n",
        "*   **`.../raw_outputs`**: The full, unprocessed text generated by the autorater. This is extremely useful for debugging, as it contains the autorater's \"chain of thought\" and reasoning for its final verdict."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LplNi6MdGPKp"
      },
      "outputs": [],
      "source": [
        "notebook_utils.display_eval_result(eval_result=eval_result)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "etgzK1iuFRHS"
      },
      "outputs": [],
      "source": [
        "notebook_utils.display_explanations(eval_result=eval_result, num=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vqY-bXh9OgZe"
      },
      "source": [
        "## Customize your metric - Text Quality\n",
        "\n",
        "Predefined metrics are great, but what if you have very specific requirements?\n",
        "\n",
        "In this section, we'll switch to a new use case—evaluating text summarization—and build our own **custom rubric-based metric.**\n",
        "\n",
        "This gives you complete control over the evaluation process, from how the rubric questions are generated to how the final critique is performed.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PF8v_5mgKcTY"
      },
      "source": [
        "### Create a text quality dataset\n",
        "\n",
        "We will create a simple dataset consisting of different prompts that ask for summarization of various text inputs.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4sVsG6R2WOc_"
      },
      "outputs": [],
      "source": [
        "tq_prompt = [\n",
        "    \"Summarize the following meeting transcript, focusing on the main decisions and action items. Transcript: 'Alex: OK, team. The primary goal for Q3 is to launch the new feature set, codenamed 'Phoenix'. Sarah, your team will handle the front-end development, and Mike, your team is responsible for the back-end infrastructure. The deadline for code completion is August 15th. Marketing, led by Chloe, will begin the promotional campaign on August 1st. All teams must provide weekly progress reports.'\",\n",
        "    \"Provide a one-sentence summary of the following product description. Description: 'The new Chrono-Max 5000 is a state-of-the-art timepiece featuring a self-winding 25-jewel Swiss movement, a scratch-resistant sapphire crystal display, and a waterproof titanium case rated for depths up to 200 meters. It also includes a perpetual calendar, a moon phase indicator, and a dual-time zone function, all elegantly designed for both formal and casual wear.'\",\n",
        "    \"Explain the scientific concept described below in simple terms, suitable for a middle school student. Concept: 'The theory of plate tectonics describes the large-scale motion of seven large plates and the movements of a larger number of smaller plates of the Earth's lithosphere. The movement of the plates results in seismic activity such as earthquakes, and the formation of geologic features like mountains, volcanoes, and oceanic trenches.'\",\n",
        "    \"Condense the following historical event summary into a tweet (280 characters or less). Summary: 'The Industrial Revolution, which took place from the 18th to 19th centuries, was a period during which predominantly agrarian, rural societies in Europe and America became industrial and urban. The iron and textile industries, along with the development of the steam engine, played central roles in this transformation, which also saw major social and economic changes, including the rise of factories and a new working class.'\",\n",
        "    \"Summarize the plot of the following movie. Plot: 'A young farm boy, who dreams of adventure, discovers a message from a captured princess. He embarks on a journey with a wise old mentor, a cocky pilot, and a loyal furry co-pilot to rescue her from the clutches of an evil empire and its fearsome enforcer. Along the way, he learns about a mystical energy field that grants him special abilities and ultimately joins a rebellion to restore freedom to the galaxy.'\",\n",
        "    \"Generate a brief summary of the key findings from the following research abstract. Abstract: 'Our study investigated the effects of daily meditation on stress and cognitive function in a cohort of 150 adults. Participants were randomly assigned to either a daily 20-minute guided meditation group or a control group. After eight weeks, the meditation group showed a statistically significant reduction in self-reported stress levels and improved performance on tasks measuring attention and working memory compared to the control group.'\",\n",
        "    \"Summarize the main arguments presented in the following opinion piece. Piece: 'The widespread adoption of remote work represents the most significant shift in the labor market in a century. While critics point to challenges in collaboration and corporate culture, the benefits—including increased employee flexibility and satisfaction, reduced operational costs for companies, and a positive environmental impact from less commuting—far outweigh the drawbacks. To remain competitive, businesses must embrace this new paradigm.'\",\n",
        "    \"Create a short summary of the following financial news report. Report: 'Global markets experienced a volatile week, with the Tech Index falling by 3.5% due to investor concerns over rising inflation and potential interest rate hikes by the central bank. In contrast, the commodities sector saw a surge, with oil prices reaching a two-year high amidst geopolitical tensions and supply chain disruptions. Analysts advise a cautious approach for the upcoming quarter.'\",\n",
        "    \"Condense the following recipe into three main steps. Recipe: 'To prepare classic spaghetti carbonara, first cook 400g of spaghetti in salted boiling water until al dente. While the pasta cooks, fry 150g of diced pancetta in a pan until crisp. In a separate bowl, whisk together 4 large egg yolks, 50g of grated Pecorino Romano cheese, and a generous amount of black pepper. Once the pasta is cooked, drain it, reserving some pasta water. Quickly toss the hot pasta with the egg and cheese mixture, adding the crispy pancetta and a splash of pasta water to create a creamy sauce. Serve immediately.'\",\n",
        "    \"Provide a brief summary of the following legal clause, explaining its main purpose. Clause: 'Force Majeure: Neither party shall be liable for any failure or delay in performing their obligations under this contract if such failure or delay is due to any cause beyond their reasonable control, including but not limited to acts of God, war, terrorism, civil unrest, or significant interruptions in public utilities.'\",\n",
        "]\n",
        "\n",
        "eval_dataset = pd.DataFrame(\n",
        "    {\n",
        "        \"prompt\": tq_prompt,\n",
        "    }\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "plaqw7NPKyoh"
      },
      "source": [
        "### Create your own rubric based metric\n",
        "\n",
        "With our dataset defined, the first step is to generate the rubric-based metric.  \n",
        "\n",
        "Building your own rubric-based metric gives you precise control over the entire evaluation lifecycle. You get to define not just *what* gets measured, but *how* it gets measured.\n",
        "\n",
        "About what to measure, you can provide your own `prompt_template` to instruct the autorater on exactly what makes a good set of rubric questions for your specific task. You can guide it to focus on factuality, conciseness, creativity, brand voice, or any other criteria you care about as shown below.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zz_OomzPK8fs"
      },
      "outputs": [],
      "source": [
        "rubric_gen_prompt = \"\"\"\n",
        "# Instructions\n",
        "Your task is to generate a rubric that can be used to evaluate the text quality of responses generated by an AI model. Specifically, to generate rubrics for a user prompt (<user_prompt>) that describes the properties that should hold for a good response to that prompt. Generate the rubric following the provided guidelines.\n",
        "\n",
        "# Rubric Generation Guidelines\n",
        "## Verifying key aspects of 6 high-level criteria categories\n",
        "Text Quality is evaluated with 6 high-level criteria categories as follows:\n",
        "Response Style & Format Structure\n",
        "Content Relevance & Conciseness\n",
        "Content Completeness & Detail\n",
        "Instruction Following\n",
        "Groundness OR Truthfulness / Correctness\n",
        "Harmlessness / Safety\n",
        "\n",
        "The generated rubrics should be able to verify key aspects of all 6 high-level criteria categories.\n",
        "\n",
        "## Rules of thumbs for good rubrics\n",
        "* A rubric is a granular, binary constraint expressed in the form of a question, a decomposition, or a testable criteria. Think of it as a deterministic \"yes/no\" question based on the prompt that a user or a rater can ask about the response to verify whether the response fulfilled the requirement in the prompt.\n",
        "* There are different types of constraints in the prompt. In this task, the constraints should be based on the 6 high-level criteria categories that we use to evaluate the Text Quality: Some are related to the Content Relevance & Conciseness of the response, some are related to Response Style & Format Structure, some are related to Instruction Following.\n",
        "* The goal of this task is to appropriately capture all the different constraints that verify the key aspects of the high-level criteria category and associated with the specifics in the user prompt — such that given these rubrics, anyone could determine how well and completely the model fulfilled the constraints in the prompt.\n",
        "\n",
        "## Additional constraints on the generated rubrics\n",
        "\n",
        "* Generated rubrics should be ordered according to the \"importance\" of its high-level criteria category. The importance of the 6 high-level criteria categories is ordered as follows:\n",
        "\n",
        "\"Instruction Following\" > \"Groundness OR Truthfulness / Correctness\" > \"Harmlessness / Safety\" > \"Content Relevance & Conciseness\" > \"Response Style & Format Structure\" > \"Content Completeness & Detail\"\n",
        "\n",
        "For example, generated rubrics for \"Instruction Following\" should be output first.\n",
        "\n",
        "* The number of generated rubrics for each criteria category may not be the same. We desire to include more questions for criteria categories of higher \"importance\", e.g. \"Instruction Following\", \"Groundness OR Truthfulness / Correctness\".\n",
        "\n",
        "* Not every rubric needs to be prompt-specific. Some can be prompt-agnostic. For example, rubrics for \"Harmlessness / Safety\" may mostly be prompt-agnostic.\n",
        "\n",
        "* Aim for less than 10 rubrics per criteria category and less than 20 in total.\n",
        "\n",
        "* Pay attention to the following, which are common mistakes of rubric generation:\n",
        "  * Word count or length: If the user prompt asks for an exact word count or length, the rubric should be exact to the word count or length. Do not add \"approximate\" or \"around\" to the word count or length.\n",
        "  * Reference content: If the user prompt contains a reference to a specific document or a specific context, the rubric should be specific to the reference content.\n",
        "  * The rubric should be specific to the reference content in the user prompt: If the user prompt contains a reference to a specific document or a specific context, the rubric should be specific to the reference content.\n",
        "  * The rubric should avoid hallucination: Do not generate rubrics that are not based on the user prompt.\n",
        "  * The rubric should be logically correct: When the user prompt is a math word problem or a science problem or a data analysis problem, the rubrics should be logically correct.\n",
        "  * The rubric should be concise: Do not generate repeated rubrics, including different rubrics that are semantically similar.\n",
        "\n",
        "# Iteratively generate rubrics\n",
        "Thoroughly examine the user prompt, generate rubrics for the given user prompt following the above Rubric Generation Guidelines. Review your answer, correct your mistakes and produce a revised answer. Do not exceed 3 iterations in total. Output your final answer for the generated rubrics.\n",
        "\n",
        "# Output format.\n",
        "Write your final output in JSON according to this schema:\n",
        "\n",
        "```json\n",
        "{{\n",
        " \"questions\": [\n",
        "   \"question 1 ...\",\n",
        "   \"question 2 ...\",\n",
        "   \"question 3 ...\",\n",
        " ],\n",
        "}}\n",
        "```\n",
        "\n",
        "IMPORTANT: Do not respond to the <user_prompt>. Only generate the rubric questions for the prompt.\n",
        "\n",
        "User prompt\n",
        "<user_prompt>\n",
        "{prompt}\n",
        "</user_prompt>\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dEBNTIz0hyHM"
      },
      "source": [
        "About how, you can provide a `metric_prompt_template` that instructs the autorater on how to evaluate a response against the rubric. You can guide its \"chain of thought\" and define the exact scoring logic it should follow."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZbqiGTa2LQ8j"
      },
      "outputs": [],
      "source": [
        "rubric_critique_prompt = \"\"\"\n",
        "# Instructions\n",
        "Your task is to evaluate the text quality of responses generated by an AI model. You will be presented with a user prompt, the model's response to that user prompt, and a series of questions against which the text quality of the response will be judged.\n",
        "\n",
        "# Rubric\n",
        "[[YES]]: The model's response fulfilled the question.\n",
        "[[NO]]: The model's response did not fulfill the question.\n",
        "\n",
        "# Follow these steps for each question:\n",
        "STEP 1: Repeat the question.\n",
        "STEP 2: Determine the steps needed to **exactly**, **precisely** and **completely** answer the question.\n",
        "STEP 3: Follow the steps outlined in STEP 2, thinking out loud.\n",
        "STEP 4: Review the thoughts and the original question.\n",
        "STEP 5: Output the final verdict.\n",
        "\n",
        "# Output format:\n",
        "<question>\n",
        "STEP 1: ...\n",
        "STEP 2: ...\n",
        "STEP 3: ...\n",
        "STEP 4: ...\n",
        "STEP 5: ...\n",
        "Question: repeat the original question\n",
        "Verdict: [[YES|NO]]\n",
        "</question>\n",
        "\n",
        "<question>\n",
        "...\n",
        "\n",
        "# User Inputs, AI-generated Response, and Rubrics\n",
        "## User Inputs\n",
        "### Prompt\n",
        "{prompt}\n",
        "\n",
        "## AI-generated Response\n",
        "{response}\n",
        "\n",
        "## Rubrics\n",
        "{rubrics}\n",
        "\n",
        "REMEMBER: Your answer will help improve the AI model. It is important to answer the question correctly. Even answering \"no\" will improve the model!\n",
        "\n",
        "Evaluation:\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vU0px_REbF1e"
      },
      "source": [
        "### Rubric Generation and Revision\n",
        "\n",
        "With our custom rubric-based metric component defined, you are ready to generate the rubrics using templates you defined, the `RubricBasedMetric` and its `generate_rubrics()` method.\n",
        "\n",
        "> Notice that you have several key parameters you can customize such as parsing functions to transform that text into a clean, structured list of questions that the SDK can use (Rubric) and to process the autorater's detailed critique, extracting the final score and verdict pairs from its raw output (Critique).\n",
        "\n",
        "Let's display the generated rubrics to see what the autorater came up with."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "U0fgxdYRJpvo"
      },
      "outputs": [],
      "source": [
        "rbm = RubricBasedMetric(\n",
        "    generation_config=RubricGenerationConfig(\n",
        "        prompt_template=rubric_gen_prompt, parsing_fn=utils.parse_rubrics\n",
        "    ),\n",
        "    critique_metric=PointwiseMetric(\n",
        "        metric=\"custom_rubric_based_text_quality\",\n",
        "        metric_prompt_template=rubric_critique_prompt,\n",
        "        custom_output_config=CustomOutputConfig(\n",
        "            return_raw_output=True, parsing_fn=utils.parse_pointwise_rubric_result\n",
        "        ),\n",
        "    ),\n",
        ")\n",
        "\n",
        "data_with_rubrics = rbm.generate_rubrics(eval_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dSMPnhsDjlj4"
      },
      "source": [
        "Let's visualize generated rubrics. This step gives you the opportunity for **human-in-the-loop revision**. Before using these questions for the final evaluation, you can inspect them, edit them, or add new ones to ensure they perfectly match your quality criteria. This is a best practice for building a robust and trustworthy evaluation pipeline."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "k_PkezBCMBcI"
      },
      "outputs": [],
      "source": [
        "display_df(data_with_rubrics, num_rows=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mBX3IgYtLrLc"
      },
      "source": [
        "### Rubric Critiquing\n",
        "\n",
        "Now that we have our dataset enriched with our custom rubrics, we can proceed to the final step: critiquing the model's responses.\n",
        "\n",
        "We'll create a new `EvalTask` and pass it our DataFrame, which now includes the `rubrics` column. When we call `eval_task.evaluate()`, the SDK detects the existing rubrics and will **skip the generation step**, moving directly to the critique.\n",
        "\n",
        "For this evaluation, we'll perform a **pointwise** evaluation, meaning each response is scored individually based on the rubric, rather than being compared to a baseline. We'll have the SDK generate responses by passing a Gemini model to the `evaluate` method."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RzbJNI0zP2-z"
      },
      "outputs": [],
      "source": [
        "eval_task = EvalTask(\n",
        "    dataset=data_with_rubrics,\n",
        "    metrics=[rbm],\n",
        ")\n",
        "\n",
        "eval_result = eval_task.evaluate(model=GenerativeModel(\"gemini-2.0-flash\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e9pA120ykFiq"
      },
      "source": [
        "### Visualize and understand eval results for custom rubric metric\n",
        "\n",
        "As before, you can use helpers function to visualize evaluation results. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HvU0g1ucUiic"
      },
      "outputs": [],
      "source": [
        "notebook_utils.display_eval_result(eval_result=eval_result)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gF3BshqpiDjN"
      },
      "outputs": [],
      "source": [
        "notebook_utils.display_explanations(eval_result=eval_result, num=1)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "multimodal_text_quality_rubric_evaluation.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
