This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d59db11cb5 Add RunEvaluationOperator for Google Vertex AI Rapid 
Evaluation API (#41940)
d59db11cb5 is described below

commit d59db11cb5cc9764cbbee753141c533898a37bf7
Author: Christian Yarros <[email protected]>
AuthorDate: Mon Sep 2 07:14:41 2024 -0400

    Add RunEvaluationOperator for Google Vertex AI Rapid Evaluation API (#41940)
---
 .../cloud/hooks/vertex_ai/generative_model.py      | 90 +++++++++++++++++++-
 .../cloud/operators/vertex_ai/generative_model.py  | 99 ++++++++++++++++++++++
 airflow/providers/google/provider.yaml             |  1 +
 .../operators/cloud/vertex_ai.rst                  | 10 +++
 generated/provider_dependencies.json               |  1 +
 .../cloud/hooks/vertex_ai/test_generative_model.py | 67 ++++++++++++++-
 .../operators/vertex_ai/test_generative_model.py   | 89 +++++++++++++++++++
 .../example_vertex_ai_generative_model.py          | 47 ++++++++++
 8 files changed, 400 insertions(+), 4 deletions(-)

diff --git a/airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py 
b/airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py
index 2fe73221bf..acadbb788a 100644
--- a/airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py
+++ b/airflow/providers/google/cloud/hooks/vertex_ai/generative_model.py
@@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, Sequence
 import vertexai
 from vertexai.generative_models import GenerativeModel, Part
 from vertexai.language_models import TextEmbeddingModel, TextGenerationModel
+from vertexai.preview.evaluation import EvalResult, EvalTask
 from vertexai.preview.tuning import sft
 
 from airflow.exceptions import AirflowProviderDeprecationWarning
@@ -62,11 +63,38 @@ class GenerativeModelHook(GoogleBaseHook):
         model = TextEmbeddingModel.from_pretrained(pretrained_model)
         return model
 
-    def get_generative_model(self, pretrained_model: str) -> GenerativeModel:
+    def get_generative_model(
+        self,
+        pretrained_model: str,
+        system_instruction: str | None = None,
+        generation_config: dict | None = None,
+        safety_settings: dict | None = None,
+        tools: list | None = None,
+    ) -> GenerativeModel:
         """Return a Generative Model object."""
-        model = GenerativeModel(pretrained_model)
+        model = GenerativeModel(
+            model_name=pretrained_model,
+            system_instruction=system_instruction,
+            generation_config=generation_config,
+            safety_settings=safety_settings,
+            tools=tools,
+        )
         return model
 
+    def get_eval_task(
+        self,
+        dataset: dict,
+        metrics: list,
+        experiment: str,
+    ) -> EvalTask:
+        """Return an EvalTask object."""
+        eval_task = EvalTask(
+            dataset=dataset,
+            metrics=metrics,
+            experiment=experiment,
+        )
+        return eval_task
+
     @deprecated(
         planned_removal_date="January 01, 2025",
         use_instead="Part objects included in contents parameter of "
@@ -436,3 +464,61 @@ class GenerativeModelHook(GoogleBaseHook):
         )
 
         return response
+
+    @GoogleBaseHook.fallback_to_default_project_id
+    def run_evaluation(
+        self,
+        pretrained_model: str,
+        eval_dataset: dict,
+        metrics: list,
+        experiment_name: str,
+        experiment_run_name: str,
+        prompt_template: str,
+        location: str,
+        generation_config: dict | None = None,
+        safety_settings: dict | None = None,
+        system_instruction: str | None = None,
+        tools: list | None = None,
+        project_id: str = PROVIDE_PROJECT_ID,
+    ) -> EvalResult:
+        """
+        Use the Rapid Evaluation API to evaluate a model.
+
+        :param pretrained_model: Required. A pre-trained model optimized for 
performing natural
+            language tasks such as classification, summarization, extraction, 
content
+            creation, and ideation.
+        :param eval_dataset: Required. A fixed dataset for evaluating a model 
against. Adheres to Rapid Evaluation API.
+        :param metrics: Required. A list of evaluation metrics to be used in 
the experiment. Adheres to Rapid Evaluation API.
+        :param experiment_name: Required. The name of the evaluation 
experiment.
+        :param experiment_run_name: Required. The specific run name or ID for 
this experiment.
+        :param prompt_template: Required. The template used to format the 
model's prompts during evaluation. Adheres to Rapid Evaluation API.
+        :param project_id: Required. The ID of the Google Cloud project that 
the service belongs to.
+        :param location: Required. The ID of the Google Cloud location that 
the service belongs to.
+        :param generation_config: Optional. A dictionary containing generation 
parameters for the model.
+        :param safety_settings: Optional. A dictionary specifying harm 
category thresholds for blocking model outputs.
+        :param system_instruction: Optional. An instruction given to the model 
to guide its behavior.
+        :param tools: Optional. A list of tools available to the model during 
evaluation, such as a data store.
+        """
+        vertexai.init(project=project_id, location=location, 
credentials=self.get_credentials())
+
+        model = self.get_generative_model(
+            pretrained_model=pretrained_model,
+            system_instruction=system_instruction,
+            generation_config=generation_config,
+            safety_settings=safety_settings,
+            tools=tools,
+        )
+
+        eval_task = self.get_eval_task(
+            dataset=eval_dataset,
+            metrics=metrics,
+            experiment=experiment_name,
+        )
+
+        eval_result = eval_task.evaluate(
+            model=model,
+            prompt_template=prompt_template,
+            experiment_run_name=experiment_run_name,
+        )
+
+        return eval_result
diff --git 
a/airflow/providers/google/cloud/operators/vertex_ai/generative_model.py 
b/airflow/providers/google/cloud/operators/vertex_ai/generative_model.py
index 3a49035002..b0b9462e21 100644
--- a/airflow/providers/google/cloud/operators/vertex_ai/generative_model.py
+++ b/airflow/providers/google/cloud/operators/vertex_ai/generative_model.py
@@ -736,3 +736,102 @@ class CountTokensOperator(GoogleCloudBaseOperator):
         self.xcom_push(context, key="total_billable_characters", 
value=response.total_billable_characters)
 
         return types_v1beta1.CountTokensResponse.to_dict(response)
+
+
+class RunEvaluationOperator(GoogleCloudBaseOperator):
+    """
+    Use the Rapid Evaluation API to evaluate a model.
+
+    :param pretrained_model: Required. A pre-trained model optimized for 
performing natural
+        language tasks such as classification, summarization, extraction, 
content
+        creation, and ideation.
+    :param eval_dataset: Required. A fixed dataset for evaluating a model 
against. Adheres to Rapid Evaluation API.
+    :param metrics: Required. A list of evaluation metrics to be used in the 
experiment. Adheres to Rapid Evaluation API.
+    :param experiment_name: Required. The name of the evaluation experiment.
+    :param experiment_run_name: Required. The specific run name or ID for this 
experiment.
+    :param prompt_template: Required. The template used to format the model's 
prompts during evaluation. Adheres to Rapid Evaluation API.
+    :param project_id: Required. The ID of the Google Cloud project that the 
service belongs to.
+    :param location: Required. The ID of the Google Cloud location that the 
service belongs to.
+    :param generation_config: Optional. A dictionary containing generation 
parameters for the model.
+    :param safety_settings: Optional. A dictionary specifying harm category 
thresholds for blocking model outputs.
+    :param system_instruction: Optional. An instruction given to the model to 
guide its behavior.
+    :param tools: Optional. A list of tools available to the model during 
evaluation, such as a data store.
+    :param gcp_conn_id: The connection ID to use connecting to Google Cloud.
+    :param impersonation_chain: Optional service account to impersonate using 
short-term
+        credentials, or chained list of accounts required to get the 
access_token
+        of the last account in the list, which will be impersonated in the 
request.
+        If set as a string, the account must grant the originating account
+        the Service Account Token Creator IAM role.
+        If set as a sequence, the identities from the list must grant
+        Service Account Token Creator IAM role to the directly preceding 
identity, with first
+        account from the list granting this role to the originating account 
(templated).
+    """
+
+    template_fields = (
+        "location",
+        "project_id",
+        "impersonation_chain",
+        "pretrained_model",
+        "eval_dataset",
+        "prompt_template",
+        "experiment_name",
+        "experiment_run_name",
+    )
+
+    def __init__(
+        self,
+        *,
+        pretrained_model: str,
+        eval_dataset: dict,
+        metrics: list,
+        experiment_name: str,
+        experiment_run_name: str,
+        prompt_template: str,
+        project_id: str,
+        location: str,
+        generation_config: dict | None = None,
+        safety_settings: dict | None = None,
+        system_instruction: str | None = None,
+        tools: list | None = None,
+        gcp_conn_id: str = "google_cloud_default",
+        impersonation_chain: str | Sequence[str] | None = None,
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+
+        self.pretrained_model = pretrained_model
+        self.eval_dataset = eval_dataset
+        self.metrics = metrics
+        self.experiment_name = experiment_name
+        self.experiment_run_name = experiment_run_name
+        self.prompt_template = prompt_template
+        self.system_instruction = system_instruction
+        self.generation_config = generation_config
+        self.safety_settings = safety_settings
+        self.tools = tools
+        self.project_id = project_id
+        self.location = location
+        self.gcp_conn_id = gcp_conn_id
+        self.impersonation_chain = impersonation_chain
+
+    def execute(self, context: Context):
+        self.hook = GenerativeModelHook(
+            gcp_conn_id=self.gcp_conn_id,
+            impersonation_chain=self.impersonation_chain,
+        )
+        response = self.hook.run_evaluation(
+            pretrained_model=self.pretrained_model,
+            eval_dataset=self.eval_dataset,
+            metrics=self.metrics,
+            experiment_name=self.experiment_name,
+            experiment_run_name=self.experiment_run_name,
+            prompt_template=self.prompt_template,
+            project_id=self.project_id,
+            location=self.location,
+            system_instruction=self.system_instruction,
+            generation_config=self.generation_config,
+            safety_settings=self.safety_settings,
+            tools=self.tools,
+        )
+
+        return response.summary_metrics
diff --git a/airflow/providers/google/provider.yaml 
b/airflow/providers/google/provider.yaml
index 6311001448..46d82050ff 100644
--- a/airflow/providers/google/provider.yaml
+++ b/airflow/providers/google/provider.yaml
@@ -169,6 +169,7 @@ dependencies:
   - sqlalchemy-bigquery>=1.2.1
   - sqlalchemy-spanner>=1.6.2
   - tenacity>=8.1.0
+  - immutabledict>=4.2.0
 
 additional-extras:
   - name: apache.beam
diff --git a/docs/apache-airflow-providers-google/operators/cloud/vertex_ai.rst 
b/docs/apache-airflow-providers-google/operators/cloud/vertex_ai.rst
index 87c7b81bc9..db23d4dfea 100644
--- a/docs/apache-airflow-providers-google/operators/cloud/vertex_ai.rst
+++ b/docs/apache-airflow-providers-google/operators/cloud/vertex_ai.rst
@@ -636,6 +636,16 @@ The operator returns the total tokens in :ref:`XCom 
<concepts:xcom>` under ``tot
     :start-after: [START how_to_cloud_vertex_ai_count_tokens_operator]
     :end-before: [END how_to_cloud_vertex_ai_count_tokens_operator]
 
+To evaluate a model you can use
+:class:`~airflow.providers.google.cloud.operators.vertex_ai.generative_model.RunEvaluationOperator`.
+The operator returns the evaluation summary metrics in :ref:`XCom 
<concepts:xcom>` under ``summary_metrics`` key.
+
+.. exampleinclude:: 
/../../tests/system/providers/google/cloud/vertex_ai/example_vertex_ai_generative_model.py
+    :language: python
+    :dedent: 4
+    :start-after: [START how_to_cloud_vertex_ai_run_evaluation_operator]
+    :end-before: [END how_to_cloud_vertex_ai_run_evaluation_operator]
+
 Reference
 ^^^^^^^^^
 
diff --git a/generated/provider_dependencies.json 
b/generated/provider_dependencies.json
index afff3d8afb..cc1f796f75 100644
--- a/generated/provider_dependencies.json
+++ b/generated/provider_dependencies.json
@@ -655,6 +655,7 @@
       "google-cloud-workflows>=1.10.0",
       "grpcio-gcp>=0.2.2",
       "httpx>=0.25.0",
+      "immutabledict>=4.2.0",
       "json-merge-patch>=0.2",
       "looker-sdk>=22.4.0",
       "pandas-gbq>=0.7.0",
diff --git 
a/tests/providers/google/cloud/hooks/vertex_ai/test_generative_model.py 
b/tests/providers/google/cloud/hooks/vertex_ai/test_generative_model.py
index 5eff60b91d..2004eec29f 100644
--- a/tests/providers/google/cloud/hooks/vertex_ai/test_generative_model.py
+++ b/tests/providers/google/cloud/hooks/vertex_ai/test_generative_model.py
@@ -27,8 +27,8 @@ from airflow.exceptions import (
 
 # For no Pydantic environment, we need to skip the tests
 pytest.importorskip("google.cloud.aiplatform_v1")
-vertexai = pytest.importorskip("vertexai.generative_models")
 from vertexai.generative_models import HarmBlockThreshold, HarmCategory, Tool, 
grounding
+from vertexai.preview.evaluation import MetricPromptTemplateExamples
 
 from airflow.providers.google.cloud.hooks.vertex_ai.generative_model import (
     GenerativeModelHook,
@@ -73,6 +73,38 @@ TEST_MIME_TYPE = "image/jpeg"
 SOURCE_MODEL = "gemini-1.0-pro-002"
 TRAIN_DATASET = 
"gs://cloud-samples-data/ai-platform/generative_ai/sft_train_data.jsonl"
 
+TEST_EVAL_DATASET = {
+    "context": [
+        "To make a classic spaghetti carbonara, start by bringing a large pot 
of salted water to a boil. While the water is heating up, cook pancetta or 
guanciale in a skillet with olive oil over medium heat until it's crispy and 
golden brown. Once the pancetta is done, remove it from the skillet and set it 
aside. In the same skillet, whisk together eggs, grated Parmesan cheese, and 
black pepper to make the sauce. When the pasta is cooked al dente, drain it and 
immediately toss it in the [...]
+        "Preparing a perfect risotto requires patience and attention to 
detail. Begin by heating butter in a large, heavy-bottomed pot over medium 
heat. Add finely chopped onions and minced garlic to the pot, and cook until 
they're soft and translucent, about 5 minutes. Next, add Arborio rice to the 
pot and cook, stirring constantly, until the grains are coated with the butter 
and begin to toast slightly. Pour in a splash of white wine and cook until it's 
absorbed. From there, gradually  [...]
+        "For a flavorful grilled steak, start by choosing a well-marbled cut 
of beef like ribeye or New York strip. Season the steak generously with kosher 
salt and freshly ground black pepper on both sides, pressing the seasoning into 
the meat. Preheat a grill to high heat and brush the grates with oil to prevent 
sticking. Place the seasoned steak on the grill and cook for about 4-5 minutes 
on each side for medium-rare, or adjust the cooking time to your desired level 
of doneness. Let t [...]
+        "Creating a creamy homemade tomato soup is a comforting and simple 
process. Begin by heating olive oil in a large pot over medium heat. Add diced 
onions and minced garlic to the pot and cook until they're soft and fragrant. 
Next, add chopped fresh tomatoes, chicken or vegetable broth, and a sprig of 
fresh basil to the pot. Simmer the soup for about 20-30 minutes, or until the 
tomatoes are tender and falling apart. Remove the basil sprig and use an 
immersion blender to puree the s [...]
+        "To bake a decadent chocolate cake from scratch, start by preheating 
your oven to 350°F (175°C) and greasing and flouring two 9-inch round cake 
pans. In a large mixing bowl, cream together softened butter and granulated 
sugar until light and fluffy. Beat in eggs one at a time, making sure each egg 
is fully incorporated before adding the next. In a separate bowl, sift together 
all-purpose flour, cocoa powder, baking powder, baking soda, and salt. Divide 
the batter evenly between t [...]
+    ],
+    "instruction": ["Summarize the following article"] * 5,
+    "reference": [
+        "The process of making spaghetti carbonara involves boiling pasta, 
crisping pancetta or guanciale, whisking together eggs and Parmesan cheese, and 
tossing everything together to create a creamy sauce.",
+        "Preparing risotto entails sautéing onions and garlic, toasting 
Arborio rice, adding wine and broth gradually, and stirring until creamy and 
tender.",
+        "Grilling a flavorful steak involves seasoning generously, preheating 
the grill, cooking to desired doneness, and letting it rest before slicing.",
+        "Creating homemade tomato soup includes sautéing onions and garlic, 
simmering with tomatoes and broth, pureeing until smooth, and seasoning to 
taste.",
+        "Baking a decadent chocolate cake requires creaming butter and sugar, 
beating in eggs and alternating dry ingredients with buttermilk before baking 
until done.",
+    ],
+}
+TEST_METRICS = [
+    MetricPromptTemplateExamples.Pointwise.SUMMARIZATION_QUALITY,
+    MetricPromptTemplateExamples.Pointwise.GROUNDEDNESS,
+    MetricPromptTemplateExamples.Pointwise.VERBOSITY,
+    MetricPromptTemplateExamples.Pointwise.INSTRUCTION_FOLLOWING,
+    "exact_match",
+    "bleu",
+    "rouge_1",
+    "rouge_2",
+    "rouge_l_sum",
+]
+TEST_EXPERIMENT_NAME = "eval-experiment-airflow-operator"
+TEST_EXPERIMENT_RUN_NAME = "eval-experiment-airflow-operator-run"
+TEST_PROMPT_TEMPLATE = "{instruction}. Article: {context}. Summary:"
+
 BASE_STRING = "airflow.providers.google.common.hooks.base_google.{}"
 GENERATIVE_MODEL_STRING = 
"airflow.providers.google.cloud.hooks.vertex_ai.generative_model.{}"
 
@@ -207,7 +239,6 @@ class TestGenerativeModelWithDefaultProjectIdHook:
             train_dataset=TRAIN_DATASET,
         )
 
-        # Assertions
         mock_sft_train.assert_called_once_with(
             source_model=SOURCE_MODEL,
             train_dataset=TRAIN_DATASET,
@@ -230,3 +261,35 @@ class TestGenerativeModelWithDefaultProjectIdHook:
         mock_model.return_value.count_tokens.assert_called_once_with(
             contents=TEST_CONTENTS,
         )
+
+    
@mock.patch(GENERATIVE_MODEL_STRING.format("GenerativeModelHook.get_generative_model"))
+    
@mock.patch(GENERATIVE_MODEL_STRING.format("GenerativeModelHook.get_eval_task"))
+    def test_run_evaluation(self, mock_eval_task, mock_model) -> None:
+        self.hook.run_evaluation(
+            project_id=GCP_PROJECT,
+            location=GCP_LOCATION,
+            pretrained_model=TEST_MULTIMODAL_PRETRAINED_MODEL,
+            eval_dataset=TEST_EVAL_DATASET,
+            metrics=TEST_METRICS,
+            experiment_name=TEST_EXPERIMENT_NAME,
+            experiment_run_name=TEST_EXPERIMENT_RUN_NAME,
+            prompt_template=TEST_PROMPT_TEMPLATE,
+        )
+
+        mock_model.assert_called_once_with(
+            pretrained_model=TEST_MULTIMODAL_PRETRAINED_MODEL,
+            system_instruction=None,
+            generation_config=None,
+            safety_settings=None,
+            tools=None,
+        )
+        mock_eval_task.assert_called_once_with(
+            dataset=TEST_EVAL_DATASET,
+            metrics=TEST_METRICS,
+            experiment=TEST_EXPERIMENT_NAME,
+        )
+        mock_eval_task.return_value.evaluate.assert_called_once_with(
+            model=mock_model.return_value,
+            prompt_template=TEST_PROMPT_TEMPLATE,
+            experiment_run_name=TEST_EXPERIMENT_RUN_NAME,
+        )
diff --git 
a/tests/providers/google/cloud/operators/vertex_ai/test_generative_model.py 
b/tests/providers/google/cloud/operators/vertex_ai/test_generative_model.py
index ada9d7843d..3745850e72 100644
--- a/tests/providers/google/cloud/operators/vertex_ai/test_generative_model.py
+++ b/tests/providers/google/cloud/operators/vertex_ai/test_generative_model.py
@@ -29,6 +29,7 @@ pytest.importorskip("google.cloud.aiplatform_v1")
 pytest.importorskip("google.cloud.aiplatform_v1beta1")
 vertexai = pytest.importorskip("vertexai.generative_models")
 from vertexai.generative_models import HarmBlockThreshold, HarmCategory, Tool, 
grounding
+from vertexai.preview.evaluation import MetricPromptTemplateExamples
 
 from airflow.providers.google.cloud.operators.vertex_ai.generative_model 
import (
     CountTokensOperator,
@@ -37,6 +38,7 @@ from 
airflow.providers.google.cloud.operators.vertex_ai.generative_model import
     PromptLanguageModelOperator,
     PromptMultimodalModelOperator,
     PromptMultimodalModelWithMediaOperator,
+    RunEvaluationOperator,
     SupervisedFineTuningTrainOperator,
     TextEmbeddingModelGetEmbeddingsOperator,
     TextGenerationModelPredictOperator,
@@ -448,3 +450,90 @@ class TestVertexAICountTokensOperator:
             contents=contents,
             pretrained_model=pretrained_model,
         )
+
+
+class TestVertexAIRunEvaluationOperator:
+    @mock.patch(VERTEX_AI_PATH.format("generative_model.GenerativeModelHook"))
+    def test_execute(
+        self,
+        mock_hook,
+    ):
+        tools = 
[Tool.from_google_search_retrieval(grounding.GoogleSearchRetrieval())]
+        pretrained_model = "gemini-pro"
+        safety_settings = {
+            HarmCategory.HARM_CATEGORY_HATE_SPEECH: 
HarmBlockThreshold.BLOCK_ONLY_HIGH,
+            HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 
HarmBlockThreshold.BLOCK_ONLY_HIGH,
+            HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 
HarmBlockThreshold.BLOCK_ONLY_HIGH,
+            HarmCategory.HARM_CATEGORY_HARASSMENT: 
HarmBlockThreshold.BLOCK_ONLY_HIGH,
+        }
+        generation_config = {"max_output_tokens": 256, "top_p": 0.8, 
"temperature": 0.0}
+
+        eval_dataset = {
+            "context": [
+                "To make a classic spaghetti carbonara, start by bringing a 
large pot of salted water to a boil. While the water is heating up, cook 
pancetta or guanciale in a skillet with olive oil over medium heat until it's 
crispy and golden brown. Once the pancetta is done, remove it from the skillet 
and set it aside. In the same skillet, whisk together eggs, grated Parmesan 
cheese, and black pepper to make the sauce. When the pasta is cooked al dente, 
drain it and immediately toss i [...]
+                "Preparing a perfect risotto requires patience and attention 
to detail. Begin by heating butter in a large, heavy-bottomed pot over medium 
heat. Add finely chopped onions and minced garlic to the pot, and cook until 
they're soft and translucent, about 5 minutes. Next, add Arborio rice to the 
pot and cook, stirring constantly, until the grains are coated with the butter 
and begin to toast slightly. Pour in a splash of white wine and cook until it's 
absorbed. From there, gr [...]
+                "For a flavorful grilled steak, start by choosing a 
well-marbled cut of beef like ribeye or New York strip. Season the steak 
generously with kosher salt and freshly ground black pepper on both sides, 
pressing the seasoning into the meat. Preheat a grill to high heat and brush 
the grates with oil to prevent sticking. Place the seasoned steak on the grill 
and cook for about 4-5 minutes on each side for medium-rare, or adjust the 
cooking time to your desired level of donenes [...]
+                "Creating a creamy homemade tomato soup is a comforting and 
simple process. Begin by heating olive oil in a large pot over medium heat. Add 
diced onions and minced garlic to the pot and cook until they're soft and 
fragrant. Next, add chopped fresh tomatoes, chicken or vegetable broth, and a 
sprig of fresh basil to the pot. Simmer the soup for about 20-30 minutes, or 
until the tomatoes are tender and falling apart. Remove the basil sprig and use 
an immersion blender to pur [...]
+                "To bake a decadent chocolate cake from scratch, start by 
preheating your oven to 350°F (175°C) and greasing and flouring two 9-inch 
round cake pans. In a large mixing bowl, cream together softened butter and 
granulated sugar until light and fluffy. Beat in eggs one at a time, making 
sure each egg is fully incorporated before adding the next. In a separate bowl, 
sift together all-purpose flour, cocoa powder, baking powder, baking soda, and 
salt. Divide the batter evenly b [...]
+            ],
+            "instruction": ["Summarize the following article"] * 5,
+            "reference": [
+                "The process of making spaghetti carbonara involves boiling 
pasta, crisping pancetta or guanciale, whisking together eggs and Parmesan 
cheese, and tossing everything together to create a creamy sauce.",
+                "Preparing risotto entails sautéing onions and garlic, 
toasting Arborio rice, adding wine and broth gradually, and stirring until 
creamy and tender.",
+                "Grilling a flavorful steak involves seasoning generously, 
preheating the grill, cooking to desired doneness, and letting it rest before 
slicing.",
+                "Creating homemade tomato soup includes sautéing onions and 
garlic, simmering with tomatoes and broth, pureeing until smooth, and seasoning 
to taste.",
+                "Baking a decadent chocolate cake requires creaming butter and 
sugar, beating in eggs and alternating dry ingredients with buttermilk before 
baking until done.",
+            ],
+        }
+        metrics = [
+            MetricPromptTemplateExamples.Pointwise.SUMMARIZATION_QUALITY,
+            MetricPromptTemplateExamples.Pointwise.GROUNDEDNESS,
+            MetricPromptTemplateExamples.Pointwise.VERBOSITY,
+            MetricPromptTemplateExamples.Pointwise.INSTRUCTION_FOLLOWING,
+            "exact_match",
+            "bleu",
+            "rouge_1",
+            "rouge_2",
+            "rouge_l_sum",
+        ]
+        experiment_name = "eval-experiment-airflow-operator"
+        experiment_run_name = "eval-experiment-airflow-operator-run"
+        prompt_template = "{instruction}. Article: {context}. Summary:"
+        system_instruction = "be concise."
+
+        op = RunEvaluationOperator(
+            task_id=TASK_ID,
+            pretrained_model=pretrained_model,
+            eval_dataset=eval_dataset,
+            metrics=metrics,
+            experiment_name=experiment_name,
+            experiment_run_name=experiment_run_name,
+            prompt_template=prompt_template,
+            system_instruction=system_instruction,
+            generation_config=generation_config,
+            safety_settings=safety_settings,
+            tools=tools,
+            project_id=GCP_PROJECT,
+            location=GCP_LOCATION,
+            gcp_conn_id=GCP_CONN_ID,
+            impersonation_chain=IMPERSONATION_CHAIN,
+        )
+        op.execute(context={"ti": mock.MagicMock()})
+        mock_hook.assert_called_once_with(
+            gcp_conn_id=GCP_CONN_ID,
+            impersonation_chain=IMPERSONATION_CHAIN,
+        )
+        mock_hook.return_value.run_evaluation.assert_called_once_with(
+            project_id=GCP_PROJECT,
+            location=GCP_LOCATION,
+            pretrained_model=pretrained_model,
+            eval_dataset=eval_dataset,
+            metrics=metrics,
+            experiment_name=experiment_name,
+            experiment_run_name=experiment_run_name,
+            prompt_template=prompt_template,
+            system_instruction=system_instruction,
+            generation_config=generation_config,
+            safety_settings=safety_settings,
+            tools=tools,
+        )
diff --git 
a/tests/system/providers/google/cloud/vertex_ai/example_vertex_ai_generative_model.py
 
b/tests/system/providers/google/cloud/vertex_ai/example_vertex_ai_generative_model.py
index d28fa89c98..f9fe332b0a 100644
--- 
a/tests/system/providers/google/cloud/vertex_ai/example_vertex_ai_generative_model.py
+++ 
b/tests/system/providers/google/cloud/vertex_ai/example_vertex_ai_generative_model.py
@@ -26,11 +26,13 @@ import os
 from datetime import datetime
 
 from vertexai.generative_models import HarmBlockThreshold, HarmCategory, Tool, 
grounding
+from vertexai.preview.evaluation import MetricPromptTemplateExamples
 
 from airflow.models.dag import DAG
 from airflow.providers.google.cloud.operators.vertex_ai.generative_model 
import (
     CountTokensOperator,
     GenerativeModelGenerateContentOperator,
+    RunEvaluationOperator,
     TextEmbeddingModelGetEmbeddingsOperator,
     TextGenerationModelPredictOperator,
 )
@@ -56,6 +58,37 @@ SAFETY_SETTINGS = {
     HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 
HarmBlockThreshold.BLOCK_ONLY_HIGH,
     HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
 }
+EVAL_DATASET = {
+    "context": [
+        "To make a classic spaghetti carbonara, start by bringing a large pot 
of salted water to a boil. While the water is heating up, cook pancetta or 
guanciale in a skillet with olive oil over medium heat until it's crispy and 
golden brown. Once the pancetta is done, remove it from the skillet and set it 
aside. In the same skillet, whisk together eggs, grated Parmesan cheese, and 
black pepper to make the sauce. When the pasta is cooked al dente, drain it and 
immediately toss it in the [...]
+        "Preparing a perfect risotto requires patience and attention to 
detail. Begin by heating butter in a large, heavy-bottomed pot over medium 
heat. Add finely chopped onions and minced garlic to the pot, and cook until 
they're soft and translucent, about 5 minutes. Next, add Arborio rice to the 
pot and cook, stirring constantly, until the grains are coated with the butter 
and begin to toast slightly. Pour in a splash of white wine and cook until it's 
absorbed. From there, gradually  [...]
+        "For a flavorful grilled steak, start by choosing a well-marbled cut 
of beef like ribeye or New York strip. Season the steak generously with kosher 
salt and freshly ground black pepper on both sides, pressing the seasoning into 
the meat. Preheat a grill to high heat and brush the grates with oil to prevent 
sticking. Place the seasoned steak on the grill and cook for about 4-5 minutes 
on each side for medium-rare, or adjust the cooking time to your desired level 
of doneness. Let t [...]
+        "Creating a creamy homemade tomato soup is a comforting and simple 
process. Begin by heating olive oil in a large pot over medium heat. Add diced 
onions and minced garlic to the pot and cook until they're soft and fragrant. 
Next, add chopped fresh tomatoes, chicken or vegetable broth, and a sprig of 
fresh basil to the pot. Simmer the soup for about 20-30 minutes, or until the 
tomatoes are tender and falling apart. Remove the basil sprig and use an 
immersion blender to puree the s [...]
+        "To bake a decadent chocolate cake from scratch, start by preheating 
your oven to 350°F (175°C) and greasing and flouring two 9-inch round cake 
pans. In a large mixing bowl, cream together softened butter and granulated 
sugar until light and fluffy. Beat in eggs one at a time, making sure each egg 
is fully incorporated before adding the next. In a separate bowl, sift together 
all-purpose flour, cocoa powder, baking powder, baking soda, and salt. Divide 
the batter evenly between t [...]
+    ],
+    "instruction": ["Summarize the following article"] * 5,
+    "reference": [
+        "The process of making spaghetti carbonara involves boiling pasta, 
crisping pancetta or guanciale, whisking together eggs and Parmesan cheese, and 
tossing everything together to create a creamy sauce.",
+        "Preparing risotto entails sautéing onions and garlic, toasting 
Arborio rice, adding wine and broth gradually, and stirring until creamy and 
tender.",
+        "Grilling a flavorful steak involves seasoning generously, preheating 
the grill, cooking to desired doneness, and letting it rest before slicing.",
+        "Creating homemade tomato soup includes sautéing onions and garlic, 
simmering with tomatoes and broth, pureeing until smooth, and seasoning to 
taste.",
+        "Baking a decadent chocolate cake requires creaming butter and sugar, 
beating in eggs and alternating dry ingredients with buttermilk before baking 
until done.",
+    ],
+}
+METRICS = [
+    MetricPromptTemplateExamples.Pointwise.SUMMARIZATION_QUALITY,
+    MetricPromptTemplateExamples.Pointwise.GROUNDEDNESS,
+    MetricPromptTemplateExamples.Pointwise.VERBOSITY,
+    MetricPromptTemplateExamples.Pointwise.INSTRUCTION_FOLLOWING,
+    "exact_match",
+    "bleu",
+    "rouge_1",
+    "rouge_2",
+    "rouge_l_sum",
+]
+EXPERIMENT_NAME = "eval-experiment-airflow-operator"
+EXPERIMENT_RUN_NAME = "eval-experiment-airflow-operator-run"
+PROMPT_TEMPLATE = "{instruction}. Article: {context}. Summary:"
 
 with DAG(
     dag_id=DAG_ID,
@@ -108,6 +141,20 @@ with DAG(
     )
     # [END how_to_cloud_vertex_ai_generative_model_generate_content_operator]
 
+    # [START how_to_cloud_vertex_ai_run_evaluation_operator]
+    run_evaluation_task = RunEvaluationOperator(
+        task_id="run_evaluation_task",
+        project_id=PROJECT_ID,
+        location=REGION,
+        pretrained_model=MULTIMODAL_MODEL,
+        eval_dataset=EVAL_DATASET,
+        metrics=METRICS,
+        experiment_name=EXPERIMENT_NAME,
+        experiment_run_name=EXPERIMENT_RUN_NAME,
+        prompt_template=PROMPT_TEMPLATE,
+    )
+    # [END how_to_cloud_vertex_ai_run_evaluation_operator]
+
     from tests.system.utils.watcher import watcher
 
     # This test needs watcher in order to properly mark success/failure

Reply via email to