Skip to content

Commit 22259db

Browse files
jsondaicopybara-github
authored andcommitted
chore: Add MetricPromptBuilder type
PiperOrigin-RevId: 768286605
1 parent 39d3c70 commit 22259db

2 files changed

Lines changed: 309 additions & 1 deletion

File tree

tests/unit/vertexai/genai/test_evals.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,147 @@ def test_inference_with_multimodal_content(
703703
)
704704

705705

706+
class TestMetricPromptBuilder:
707+
"""Unit tests for the MetricPromptBuilder class."""
708+
709+
def test_metric_prompt_builder_minimal_fields(self):
710+
criteria = {"criterion1": "definition1"}
711+
rating_scores = {"score1": "description1"}
712+
builder = vertexai_genai_types.MetricPromptBuilder(
713+
criteria=criteria,
714+
rating_scores=rating_scores,
715+
metric_definition=None,
716+
few_shot_examples=None,
717+
)
718+
assert builder.criteria == criteria
719+
assert builder.rating_scores == rating_scores
720+
expected_instruction = (
721+
"You are an expert evaluator. Your task is to evaluate the quality"
722+
" of the responses generated by AI models. We will provide you with"
723+
" the user prompt and an AI-generated responses.\nYou should first"
724+
" read the user input carefully for analyzing the task, and then"
725+
" evaluate the quality of the responses based on the Criteria"
726+
" provided in the Evaluation section below.\nYou will assign the"
727+
" response a rating following the Rating Scores and Evaluation"
728+
" Steps. Give step by step explanations for your rating, and only"
729+
" choose ratings from the Rating Scores."
730+
)
731+
expected_evaluation_steps = {
732+
"Step 1": (
733+
"Assess the response in aspects of all criteria provided. Provide"
734+
" assessment according to each criterion."
735+
),
736+
"Step 2": (
737+
"Score based on the Rating Scores. Give a brief rationale to"
738+
" explain your evaluation considering each individual criterion."
739+
),
740+
}
741+
assert builder.instruction == expected_instruction
742+
assert builder.evaluation_steps == expected_evaluation_steps
743+
assert "{response}" in builder.text
744+
745+
def test_metric_prompt_builder_all_fields(self):
746+
criteria = {"criterion1": "definition1"}
747+
rating_scores = {"score1": "description1"}
748+
instruction = "Custom instruction."
749+
metric_definition = "Custom metric definition."
750+
evaluation_steps = {"step1": "custom step 1"}
751+
few_shot_examples = ["example1", "example2"]
752+
builder = vertexai_genai_types.MetricPromptBuilder(
753+
criteria=criteria,
754+
rating_scores=rating_scores,
755+
instruction=instruction,
756+
metric_definition=metric_definition,
757+
evaluation_steps=evaluation_steps,
758+
few_shot_examples=few_shot_examples,
759+
)
760+
assert builder.criteria == criteria
761+
assert builder.rating_scores == rating_scores
762+
assert builder.instruction == instruction
763+
assert builder.metric_definition == metric_definition
764+
assert builder.evaluation_steps == evaluation_steps
765+
assert builder.few_shot_examples == few_shot_examples
766+
assert instruction in builder.text
767+
assert metric_definition in builder.text
768+
assert "custom step 1" in builder.text
769+
assert "example1" in builder.text
770+
771+
def test_metric_prompt_builder_default_instruction_and_steps_in_text(self):
772+
criteria = {"c1": "v1"}
773+
rating_scores = {"s1": "d1"}
774+
builder = vertexai_genai_types.MetricPromptBuilder(
775+
criteria=criteria,
776+
rating_scores=rating_scores,
777+
metric_definition=None,
778+
few_shot_examples=None,
779+
)
780+
expected_instruction = (
781+
"You are an expert evaluator. Your task is to evaluate the quality"
782+
" of the responses generated by AI models. We will provide you with"
783+
" the user prompt and an AI-generated responses.\nYou should first"
784+
" read the user input carefully for analyzing the task, and then"
785+
" evaluate the quality of the responses based on the Criteria"
786+
" provided in the Evaluation section below.\nYou will assign the"
787+
" response a rating following the Rating Scores and Evaluation"
788+
" Steps. Give step by step explanations for your rating, and only"
789+
" choose ratings from the Rating Scores."
790+
)
791+
expected_evaluation_steps = {
792+
"Step 1": (
793+
"Assess the response in aspects of all criteria provided. Provide"
794+
" assessment according to each criterion."
795+
),
796+
"Step 2": (
797+
"Score based on the Rating Scores. Give a brief rationale to"
798+
" explain your evaluation considering each individual criterion."
799+
),
800+
}
801+
assert expected_instruction in builder.text
802+
for step_desc in expected_evaluation_steps.values():
803+
assert step_desc in builder.text
804+
805+
def test_metric_prompt_builder_custom_instruction_and_steps_in_text(self):
806+
criteria = {"c1": "v1"}
807+
rating_scores = {"s1": "d1"}
808+
custom_instruction = "My custom instructions."
809+
custom_steps = {"Step 1": "Do this first.", "Step 2": "Then do that."}
810+
builder = vertexai_genai_types.MetricPromptBuilder(
811+
criteria=criteria,
812+
rating_scores=rating_scores,
813+
instruction=custom_instruction,
814+
evaluation_steps=custom_steps,
815+
metric_definition=None,
816+
few_shot_examples=None,
817+
)
818+
assert custom_instruction in builder.text
819+
for step_desc in custom_steps.values():
820+
assert step_desc in builder.text
821+
822+
def test_metric_prompt_builder_missing_criteria_raises_error(self):
823+
with pytest.raises(
824+
ValueError,
825+
match="Both 'criteria' and 'rating_scores' are required",
826+
):
827+
vertexai_genai_types.MetricPromptBuilder(
828+
rating_scores={"score1": "description1"},
829+
criteria=None,
830+
metric_definition=None,
831+
few_shot_examples=None,
832+
)
833+
834+
def test_metric_prompt_builder_missing_rating_scores_raises_error(self):
835+
with pytest.raises(
836+
ValueError,
837+
match="Both 'criteria' and 'rating_scores' are required",
838+
):
839+
vertexai_genai_types.MetricPromptBuilder(
840+
criteria={"criterion1": "definition1"},
841+
rating_scores=None,
842+
metric_definition=None,
843+
few_shot_examples=None,
844+
)
845+
846+
706847
class TestPromptTemplate:
707848
"""Unit tests for the PromptTemplate class."""
708849

vertexai/_genai/types.py

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from typing import Any, Callable, ClassVar, Optional, Tuple, Union
2424
from google.genai import _common
2525
from google.genai import types as genai_types
26-
from pydantic import Field, computed_field, field_validator
26+
from pydantic import Field, computed_field, field_validator, model_validator
2727
from typing_extensions import TypedDict
2828

2929
logger = logging.getLogger("vertexai_genai.types")
@@ -2463,6 +2463,173 @@ def __repr__(self) -> str:
24632463
return f"PromptTemplate(text='{self.text}')"
24642464

24652465

2466+
class MetricPromptBuilder(PromptTemplate):
2467+
"""Builder class for structured LLM-based metric prompt template."""
2468+
2469+
criteria: Optional[dict[str, str]] = Field(
2470+
None,
2471+
description="""A dictionary of criteria used to evaluate the model responses.
2472+
The keys are criterion names, and the values are the corresponding
2473+
criterion definitions.
2474+
""",
2475+
)
2476+
2477+
rating_scores: Optional[dict[str, str]] = Field(
2478+
None,
2479+
description="""A dictionary mapping of rating score names to their definitions.""",
2480+
)
2481+
2482+
@staticmethod
2483+
def _get_default_instruction() -> str:
2484+
"""Returns the default instruction for evaluation."""
2485+
return (
2486+
"You are an expert evaluator. Your task is to evaluate the quality"
2487+
" of the responses generated by AI models. We will provide you with"
2488+
" the user prompt and an AI-generated responses.\nYou should first"
2489+
" read the user input carefully for analyzing the task, and then"
2490+
" evaluate the quality of the responses based on the Criteria"
2491+
" provided in the Evaluation section below.\nYou will assign the"
2492+
" response a rating following the Rating Scores and Evaluation"
2493+
" Steps. Give step by step explanations for your rating, and only"
2494+
" choose ratings from the Rating Scores."
2495+
)
2496+
2497+
instruction: Optional[str] = Field(
2498+
default_factory=lambda: MetricPromptBuilder._get_default_instruction(),
2499+
description="""The general instruction to guide the model in performing the evaluation.
2500+
If not provided, a default instruction for evaluation will be used.
2501+
""",
2502+
)
2503+
2504+
metric_definition: Optional[str] = Field(
2505+
None,
2506+
description="""An optional high-level description of the metric to be evaluated.
2507+
If not provided, this field will not be included in the prompt template.
2508+
""",
2509+
)
2510+
2511+
@staticmethod
2512+
def _get_default_evaluation_steps() -> dict[str, str]:
2513+
"""Returns the default evaluation steps for metric evaluation."""
2514+
return {
2515+
"Step 1": (
2516+
"Assess the response in aspects of all criteria provided."
2517+
" Provide assessment according to each criterion."
2518+
),
2519+
"Step 2": (
2520+
"Score based on the Rating Scores. Give a brief rationale to"
2521+
" explain your evaluation considering each individual"
2522+
" criterion."
2523+
),
2524+
}
2525+
2526+
evaluation_steps: Optional[dict[str, str]] = Field(
2527+
default_factory=lambda: MetricPromptBuilder._get_default_evaluation_steps(),
2528+
description="""An optional dictionary of evaluation steps.
2529+
The keys are the names of the evaluation steps, and the values are
2530+
descriptions of the corresponding evaluation steps. If not provided,
2531+
default metric evaluation steps will be used.
2532+
""",
2533+
)
2534+
2535+
few_shot_examples: Optional[list[str]] = Field(
2536+
None,
2537+
description="""An optional list of few-shot examples to guide the model's evaluation.
2538+
These examples demonstrate how to apply the criteria, rating scores,
2539+
and evaluation steps to assess model responses. Providing few-shot examples
2540+
can improve the accuracy of the evaluation. If not provided, this field
2541+
will not be included in the prompt template.
2542+
""",
2543+
)
2544+
2545+
@staticmethod
2546+
def _serialize_dict_in_order(elements: Optional[dict[str, str]]) -> str:
2547+
"""Serializes dictionary to ordered string value without brackets."""
2548+
if elements is None:
2549+
return ""
2550+
return "\n".join(f"{key}: {value}" for key, value in sorted(elements.items()))
2551+
2552+
@model_validator(mode="before")
2553+
@classmethod
2554+
def _prepare_fields_and_construct_text(cls, data: Any) -> Any:
2555+
"""Pydantic model validator (before mode) to prepare and construct prompt text.
2556+
2557+
This validator performs the following actions:
2558+
1. Apply default logic for fields (instruction, evaluation_steps).
2559+
2. Construct the 'text' string from all components.
2560+
3. Ensure 'text' is in the data dictionary for PromptTemplate
2561+
initialization.
2562+
2563+
Args:
2564+
data: Input data for the model, either a dictionary or an existing
2565+
model instance.
2566+
2567+
Returns:
2568+
Processed data dictionary with the 'text' field constructed.
2569+
"""
2570+
if not isinstance(data, dict):
2571+
return data
2572+
2573+
if "text" in data:
2574+
raise ValueError(
2575+
"The 'text' field is automatically constructed and should not"
2576+
" be provided manually."
2577+
)
2578+
2579+
if data.get("criteria") is None or data.get("rating_scores") is None:
2580+
raise ValueError(
2581+
"Both 'criteria' and 'rating_scores' are required to construct"
2582+
" theLLM-based metric prompt template text."
2583+
)
2584+
2585+
instruction = data.get("instruction", cls._get_default_instruction())
2586+
metric_definition = data.get("metric_definition")
2587+
evaluation_steps = data.get(
2588+
"evaluation_steps", cls._get_default_evaluation_steps()
2589+
)
2590+
criteria = data.get("criteria")
2591+
rating_scores = data.get("rating_scores")
2592+
few_shot_examples = data.get("few_shot_examples")
2593+
2594+
template_parts = [
2595+
"# Instruction",
2596+
instruction,
2597+
"\n",
2598+
"# Evaluation",
2599+
]
2600+
2601+
sections = {
2602+
"Metric Definition": metric_definition,
2603+
"Criteria": cls._serialize_dict_in_order(criteria),
2604+
"Rating Scores": cls._serialize_dict_in_order(rating_scores),
2605+
"Evaluation Steps": cls._serialize_dict_in_order(evaluation_steps),
2606+
"Evaluation Examples": (
2607+
"\n".join(few_shot_examples) if few_shot_examples else None
2608+
),
2609+
}
2610+
2611+
for title, content in sections.items():
2612+
if content:
2613+
template_parts.extend([f"## {title}", f"{content}\n"])
2614+
2615+
template_parts.extend(
2616+
[
2617+
"\n# User Inputs and AI-generated Response",
2618+
"## User Inputs",
2619+
]
2620+
)
2621+
2622+
template_parts.extend(["## AI-generated Response", "{response}"])
2623+
constructed_text = "\n".join(template_parts)
2624+
2625+
data["text"] = constructed_text
2626+
return data
2627+
2628+
def __str__(self) -> str:
2629+
"""Returns the fully constructed prompt template text."""
2630+
return self.text
2631+
2632+
24662633
class PromptTemplateDict(TypedDict, total=False):
24672634
"""A prompt template for creating prompts with variables."""
24682635

0 commit comments

Comments
 (0)