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

606 lines
24 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "w2Nnpp1a3oKr"
},
"outputs": [],
"source": [
"# Copyright 2024 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": "mSdMTAZgF_M2"
},
"source": [
"# Evaluate Generative Model Tool Use with Custom Code Execution"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "b9ziZDnkF_j4"
},
"source": [
"<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/evaluate_with_your_python_code.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%2Fevaluate_with_your_python_code.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/evaluate_with_your_python_code.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://console.cloud.google.com/bigquery/import?url=https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaluate_with_your_python_code.ipynb\">\n",
" <img src=\"https://www.gstatic.com/images/branding/gcpiconscolors/bigquery/v1/32px.svg\" alt=\"BigQuery Studio logo\"><br> Open in BigQuery Studio\n",
" </a>\n",
" </td>\n",
" <td style=\"text-align: center\">\n",
" <a href=\"https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaluate_with_your_python_code.ipynb\">\n",
" <img width=\"32px\" src=\"https://storage.googleapis.com/github-repo/generative-ai/logos/GitHub_Invertocat_Dark.svg\" alt=\"GitHub logo\"><br> View on GitHub\n",
" </a>\n",
" </td>\n",
"</table>\n",
"\n",
"<div style=\"clear: both;\"></div>\n",
"\n",
"<p>\n",
"<b>Share to:</b>\n",
"\n",
"<a href=\"https://www.linkedin.com/sharing/share-offsite/?url=https%3A//github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/evaluation/evaluate_with_your_python_code.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/evaluate_with_your_python_code.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/evaluate_with_your_python_code.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/evaluate_with_your_python_code.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/evaluate_with_your_python_code.ipynb\" target=\"_blank\">\n",
" <img width=\"20px\" src=\"https://upload.wikimedia.org/wikipedia/commons/5/51/Facebook_f_logo_%282019%29.svg\" alt=\"Facebook logo\">\n",
"</a>\n",
"</p>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "psD4Tv-Z3oKt"
},
"source": [
"| | |\n",
"|-|-|\n",
"| Author | [Jessica Wang](https://github.com/wjess) |\n",
"\n",
"## Overview\n",
"\n",
"This notebook showcases how to use the `remote_custom_function` parameter in the Vertex AI Python SDK for Gen AI Evaluation Service to evaluate generative model tool use. Specifically, it demonstrates:\n",
"* Defining custom python methods to validate tool calls, tool names, and tool parameters.\n",
"* Executing these methods through custom remote functions against an evaluation dataset.\n",
"* Leveraging `remote_custom_function` to mathematically calculate overall Precision, Recall, and F1 scores for tool usage.\n",
"\n",
"See also:\n",
"* Learn more about [Vertex Gen AI Evaluation Service SDK](https://cloud.google.com/vertex-ai/generative-ai/docs/models/evaluation-overview)."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "XLf9nKQ53oKu"
},
"source": [
"## Getting Started\n",
"\n",
"### Install Vertex AI Python SDK for Gen AI Evaluation Service"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cCO6VFRB3oKu"
},
"outputs": [],
"source": [
"%pip install --upgrade --user --quiet google-cloud-aiplatform[evaluation]"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "CqdvHNKR3oKv"
},
"source": [
"### Authenticate your notebook environment (Colab only)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "kYyLF5Vq3oKv"
},
"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": "p83W-zR93oKv"
},
"source": [
"### Set Google Cloud project information and initialize Vertex AI SDK"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "8bRTXOY-3oKv"
},
"outputs": [],
"source": [
"PROJECT_ID = \"[your-project-id]\" # @param {type:\"string\"}\n",
"LOCATION = \"us-central1\" # @param {type:\"string\"}\n",
"\n",
"if not PROJECT_ID or PROJECT_ID == \"[your-project-id]\":\n",
" raise ValueError(\"Please set your PROJECT_ID\")\n",
"\n",
"import vertexai\n",
"\n",
"vertexai.init(project=PROJECT_ID, location=LOCATION)\n",
"client = vertexai.Client(project=PROJECT_ID, location=LOCATION)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "x9GuVowZ3oKw"
},
"source": [
"### Import libraries"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dMmsCPdE3oKw"
},
"outputs": [],
"source": [
"import json\n",
"\n",
"import pandas as pd\n",
"import vertexai\n",
"from vertexai import types"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ocrqPtj_3oKw"
},
"source": [
"## Evaluate Tool use and Function Calling quality for Gemini\n",
"\n",
"### 1. Define Custom Metrics for Tool Use\n",
"\n",
"We will use the `remote_custom_function` parameter in the `Metric` class to define how our evaluation service should compute tool use matches remotely.\n",
"\n",
"#### Tool Call Valid Metric"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "42hMilzU3oKw"
},
"outputs": [],
"source": [
"tool_call_valid_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" # Negative example: no tool calls expected\n",
" if not target.get(\"tool_calls\"):\n",
" if not response.get(\"tool_calls\"):\n",
" return 1.0 # True negative\n",
" return 0.0 # False positive\n",
"\n",
" # Tool call expected but not provided\n",
" if not response.get(\"tool_calls\"):\n",
" return 0.0 # False negative\n",
"\n",
" target_call = target[\"tool_calls\"][0]\n",
" prediction_call = response[\"tool_calls\"][0]\n",
"\n",
" if \"name\" in target_call and \"name\" in prediction_call:\n",
" return 1.0\n",
" return 0.0\n",
"\"\"\"\n",
"\n",
"tool_call_valid_metric = types.Metric(\n",
" name=\"tool_call_valid\", remote_custom_function=tool_call_valid_code\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "PrmvQ2ki3oKw"
},
"outputs": [],
"source": [
"tool_name_match_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" if not target.get(\"tool_calls\"):\n",
" return 1.0 if not response.get(\"tool_calls\") else 0.0\n",
"\n",
" if not response.get(\"tool_calls\"):\n",
" return 0.0\n",
"\n",
" target_call = target[\"tool_calls\"][0]\n",
" prediction_call = response[\"tool_calls\"][0]\n",
"\n",
" if target_call.get(\"name\") == prediction_call.get(\"name\"):\n",
" return 1.0\n",
" return 0.0\n",
"\"\"\"\n",
"\n",
"tool_name_match_metric = types.Metric(\n",
" name=\"tool_name_match\", remote_custom_function=tool_name_match_code\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "6w5g3yRn3oKw"
},
"source": [
"#### Tool Parameter Match Metrics"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jhw16VVr3oKw"
},
"outputs": [],
"source": [
"tool_parameter_key_match_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" if not target.get(\"tool_calls\"):\n",
" return 1.0 if not response.get(\"tool_calls\") else 0.0\n",
"\n",
" if not response.get(\"tool_calls\"):\n",
" return 0.0\n",
"\n",
" target_call = target[\"tool_calls\"][0]\n",
" prediction_call = response[\"tool_calls\"][0]\n",
"\n",
" target_args = target_call.get(\"arguments\", {})\n",
" prediction_args = prediction_call.get(\"arguments\", {})\n",
"\n",
" num_k_matches = sum(1 for k in target_args if k in prediction_args)\n",
" num_unique_keys = len(set(target_args.keys()) | set(prediction_args.keys()))\n",
"\n",
" return float(num_k_matches) / float(num_unique_keys) if num_unique_keys > 0 else 1.0\n",
"\"\"\"\n",
"\n",
"tool_parameter_kv_match_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" if not target.get(\"tool_calls\"):\n",
" return 1.0 if not response.get(\"tool_calls\") else 0.0\n",
"\n",
" if not response.get(\"tool_calls\"):\n",
" return 0.0\n",
"\n",
" target_call = target[\"tool_calls\"][0]\n",
" prediction_call = response[\"tool_calls\"][0]\n",
"\n",
" target_args = target_call.get(\"arguments\", {})\n",
" prediction_args = prediction_call.get(\"arguments\", {})\n",
"\n",
" num_kv_matches = 0\n",
" for k, v in target_args.items():\n",
" if k in prediction_args and prediction_args[k] == v:\n",
" num_kv_matches += 1\n",
"\n",
" num_unique_keys = len(set(target_args.keys()) | set(prediction_args.keys()))\n",
"\n",
" return float(num_kv_matches) / float(num_unique_keys) if num_unique_keys > 0 else 1.0\n",
"\"\"\"\n",
"\n",
"tool_parameter_key_match_metric = types.Metric(\n",
" name=\"tool_parameter_key_match\",\n",
" remote_custom_function=tool_parameter_key_match_code,\n",
")\n",
"\n",
"tool_parameter_kv_match_metric = types.Metric(\n",
" name=\"tool_parameter_kv_match\", remote_custom_function=tool_parameter_kv_match_code\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "rpcB08iZ3oKx"
},
"source": [
"### 2. Run Evaluation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "KX0CkUa5Yprx"
},
"outputs": [],
"source": [
"# Create a sample evaluation dataset\n",
"dataset = pd.DataFrame(\n",
" {\n",
" \"prompt\": [\n",
" \"What is the weather in Tokyo?\",\n",
" \"Tell me a joke.\",\n",
" \"Book a flight to Paris tomorrow.\",\n",
" ],\n",
" \"reference\": [\n",
" json.dumps(\n",
" {\n",
" \"content\": \"\",\n",
" \"tool_calls\": [\n",
" {\"name\": \"get_weather\", \"arguments\": {\"location\": \"Tokyo\"}}\n",
" ],\n",
" }\n",
" ),\n",
" json.dumps({\"content\": \"Here is a joke...\", \"tool_calls\": []}),\n",
" json.dumps(\n",
" {\n",
" \"content\": \"\",\n",
" \"tool_calls\": [\n",
" {\n",
" \"name\": \"book_flight\",\n",
" \"arguments\": {\"destination\": \"Paris\", \"date\": \"tomorrow\"},\n",
" }\n",
" ],\n",
" }\n",
" ),\n",
" ],\n",
" \"response\": [\n",
" json.dumps(\n",
" {\n",
" \"content\": \"\",\n",
" \"tool_calls\": [\n",
" {\"name\": \"get_weather\", \"arguments\": {\"location\": \"Tokyo\"}}\n",
" ],\n",
" }\n",
" ),\n",
" json.dumps({\"content\": \"Why did the chicken...\", \"tool_calls\": []}),\n",
" json.dumps(\n",
" {\n",
" \"content\": \"\",\n",
" \"tool_calls\": [\n",
" {\"name\": \"book_flight\", \"arguments\": {\"destination\": \"Paris\"}}\n",
" ],\n",
" }\n",
" ),\n",
" ],\n",
" }\n",
")\n",
"\n",
"eval_dataset = types.EvaluationDataset(eval_dataset_df=dataset)\n",
"\n",
"# Evaluate using our custom metrics\n",
"eval_result = client.evals.evaluate(\n",
" dataset=eval_dataset,\n",
" metrics=[\n",
" tool_call_valid_metric,\n",
" tool_name_match_metric,\n",
" tool_parameter_key_match_metric,\n",
" tool_parameter_kv_match_metric,\n",
" ],\n",
")\n",
"\n",
"eval_result.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sG744cTa3oKx"
},
"source": [
"### 3. Calculate Precision, Recall, and F1"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "Mdb_Mg7A3oKx"
},
"outputs": [],
"source": [
"# 1. Define Remote Custom Functions for Classification\n",
"tool_call_tp_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" ref_has_tool = len(target.get(\"tool_calls\", [])) > 0\n",
" pred_has_tool = len(response.get(\"tool_calls\", [])) > 0\n",
"\n",
" if ref_has_tool and pred_has_tool:\n",
" return 1.0\n",
" return 0.0\n",
"\"\"\"\n",
"\n",
"tool_call_fp_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" ref_has_tool = len(target.get(\"tool_calls\", [])) > 0\n",
" pred_has_tool = len(response.get(\"tool_calls\", [])) > 0\n",
"\n",
" if not ref_has_tool and pred_has_tool:\n",
" return 1.0\n",
" return 0.0\n",
"\"\"\"\n",
"\n",
"tool_call_fn_code = \"\"\"\n",
"import json\n",
"\n",
"def evaluate(instance: dict) -> float:\n",
" ref_text = instance['reference']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
" pred_text = instance['response']['contents']['gemini_contents'][0]['parts'][0]['text']\n",
"\n",
" target = json.loads(ref_text)\n",
" response = json.loads(pred_text)\n",
"\n",
" ref_has_tool = len(target.get(\"tool_calls\", [])) > 0\n",
" pred_has_tool = len(response.get(\"tool_calls\", [])) > 0\n",
"\n",
" if ref_has_tool and not pred_has_tool:\n",
" return 1.0\n",
" return 0.0\n",
"\"\"\"\n",
"\n",
"metric_tp = types.Metric(name=\"tool_call_tp\", remote_custom_function=tool_call_tp_code)\n",
"metric_fp = types.Metric(name=\"tool_call_fp\", remote_custom_function=tool_call_fp_code)\n",
"metric_fn = types.Metric(name=\"tool_call_fn\", remote_custom_function=tool_call_fn_code)\n",
"\n",
"# 2. Evaluate the dataset remotely\n",
"classification_eval_result = client.evals.evaluate(\n",
" dataset=eval_dataset, metrics=[metric_tp, metric_fp, metric_fn]\n",
")\n",
"\n",
"# 3. Extract the Mean Scores\n",
"mean_tp = next(\n",
" m.mean_score\n",
" for m in classification_eval_result.summary_metrics\n",
" if m.metric_name == \"tool_call_tp\"\n",
")\n",
"mean_fp = next(\n",
" m.mean_score\n",
" for m in classification_eval_result.summary_metrics\n",
" if m.metric_name == \"tool_call_fp\"\n",
")\n",
"mean_fn = next(\n",
" m.mean_score\n",
" for m in classification_eval_result.summary_metrics\n",
" if m.metric_name == \"tool_call_fn\"\n",
")\n",
"\n",
"# 4. Calculate overall Precision, Recall, and F1 mathematically\n",
"precision = mean_tp / (mean_tp + mean_fp) if (mean_tp + mean_fp) > 0 else 0.0\n",
"recall = mean_tp / (mean_tp + mean_fn) if (mean_tp + mean_fn) > 0 else 0.0\n",
"f1_score = (\n",
" 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0\n",
")\n",
"\n",
"print(\"Overall Tool Usage Metrics (Calculated via Remote Custom Functions):\")\n",
"print(f\"Precision: {precision}\")\n",
"print(f\"Recall: {recall}\")\n",
"print(f\"F1 Score: {f1_score}\")"
]
}
],
"metadata": {
"colab": {
"toc_visible": true,
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}