Skip to content

Commit 6b3269b

Browse files
jsondaicopybara-github
authored andcommitted
chore: GenAI Client(evals) - Make HTML visualization helpers public.
PiperOrigin-RevId: 914490134
1 parent a4ffb67 commit 6b3269b

2 files changed

Lines changed: 108 additions & 14 deletions

File tree

tests/unit/vertexai/genai/test_evals.py

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ def test_response_structure(self):
606606
assert result.clusters[1].cluster_id == "cluster-2"
607607

608608
def test_get_loss_analysis_html(self):
609-
"""Tests that _get_loss_analysis_html generates valid HTML with data."""
609+
"""Tests that get_loss_analysis_html generates valid HTML with data."""
610610
from vertexai._genai import _evals_visualization
611611
import json
612612

@@ -666,7 +666,7 @@ def test_get_loss_analysis_html(self):
666666
}
667667
]
668668
}
669-
html = _evals_visualization._get_loss_analysis_html(json.dumps(data))
669+
html = _evals_visualization.get_loss_analysis_html(json.dumps(data))
670670
assert "Loss Pattern Analysis" in html
671671
assert "test_metric" not in html # data is Base64-encoded in the HTML
672672
assert "<!DOCTYPE html>" in html
@@ -676,6 +676,91 @@ def test_get_loss_analysis_html(self):
676676
assert "example-section-label" in html # labels for scenario/rubrics
677677
assert "Analysis Summary" in html # summary heading
678678

679+
def test_get_evaluation_html(self):
680+
"""Tests that get_evaluation_html generates valid HTML with data."""
681+
from vertexai._genai import _evals_visualization
682+
import base64
683+
import json
684+
685+
data = {
686+
"summary_metrics": [{"metric_name": "test_metric", "mean_score": 0.85}],
687+
"eval_case_results": [
688+
{
689+
"eval_case_index": 0,
690+
"response_candidate_results": [
691+
{"display_text": "candidate response"}
692+
],
693+
}
694+
],
695+
"metadata": {"dataset": []},
696+
}
697+
payload_json = json.dumps(data)
698+
html = _evals_visualization.get_evaluation_html(payload_json)
699+
700+
assert "<!DOCTYPE html>" in html
701+
assert "<title>Evaluation Report</title>" in html
702+
assert "test_metric" not in html
703+
payload_b64 = base64.b64encode(payload_json.encode("utf-8")).decode("ascii")
704+
assert payload_b64 in html
705+
assert "DOMPurify" in html
706+
707+
def test_get_comparison_html(self):
708+
"""Tests that get_comparison_html generates valid HTML with data."""
709+
from vertexai._genai import _evals_visualization
710+
import base64
711+
import json
712+
713+
data = {
714+
"summary_metrics": [
715+
{
716+
"metric_name": "test_metric",
717+
"win_rate": 0.6,
718+
"loss_rate": 0.4,
719+
}
720+
],
721+
"eval_case_results": [
722+
{
723+
"eval_case_index": 0,
724+
"response_candidate_results": [
725+
{"display_text": "candidate A"},
726+
{"display_text": "candidate B"},
727+
],
728+
}
729+
],
730+
"metadata": {"dataset": []},
731+
}
732+
payload_json = json.dumps(data)
733+
html = _evals_visualization.get_comparison_html(payload_json)
734+
735+
assert "<!DOCTYPE html>" in html
736+
assert "<title>Eval Comparison Report</title>" in html
737+
assert "test_metric" not in html
738+
payload_b64 = base64.b64encode(payload_json.encode("utf-8")).decode("ascii")
739+
assert payload_b64 in html
740+
assert "DOMPurify" in html
741+
742+
def test_get_inference_html(self):
743+
"""Tests that get_inference_html generates valid HTML with data."""
744+
from vertexai._genai import _evals_visualization
745+
import base64
746+
import json
747+
748+
data = [
749+
{
750+
"prompt": "What is the capital of France?",
751+
"response": "Paris",
752+
}
753+
]
754+
payload_json = json.dumps(data, ensure_ascii=False)
755+
html = _evals_visualization.get_inference_html(payload_json)
756+
757+
assert "<!DOCTYPE html>" in html
758+
assert "<title>Evaluation Dataset</title>" in html
759+
assert "Paris" not in html
760+
payload_b64 = base64.b64encode(payload_json.encode("utf-8")).decode("ascii")
761+
assert payload_b64 in html
762+
assert "DOMPurify" in html
763+
679764
def test_display_loss_clusters_response_no_ipython(self):
680765
"""Tests graceful fallback when not in IPython."""
681766
from vertexai._genai import _evals_visualization
@@ -1488,7 +1573,7 @@ def test_display_loss_analysis_results_html(self):
14881573
},
14891574
ensure_ascii=False,
14901575
)
1491-
html = _evals_visualization._get_loss_analysis_html(payload_json)
1576+
html = _evals_visualization.get_loss_analysis_html(payload_json)
14921577
# The HTML is a self-contained report with base64-encoded JSON payload
14931578
# decoded by JavaScript at runtime. Verify structure, not content.
14941579
assert "<!DOCTYPE html>" in html

vertexai/_genai/_evals_visualization.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def _extract_dataset_rows(dataset: types.EvaluationDataset) -> list[dict[str, An
231231
return processed_rows
232232

233233

234-
def _get_evaluation_html(eval_result_json: str) -> str:
234+
def get_evaluation_html(eval_result_json: str) -> str:
235235
"""Returns a self-contained HTML for single evaluation visualization."""
236236
payload_b64 = _encode_to_base64(eval_result_json)
237237
return textwrap.dedent(
@@ -787,7 +787,7 @@ def _get_evaluation_html(eval_result_json: str) -> str:
787787
)
788788

789789

790-
def _get_comparison_html(eval_result_json: str) -> str:
790+
def get_comparison_html(eval_result_json: str) -> str:
791791
"""Returns a self-contained HTML for a side-by-side eval comparison."""
792792
payload_b64 = _encode_to_base64(eval_result_json)
793793
return textwrap.dedent(
@@ -1277,7 +1277,7 @@ def _get_comparison_html(eval_result_json: str) -> str:
12771277
)
12781278

12791279

1280-
def _get_inference_html(dataframe_json: str) -> str:
1280+
def get_inference_html(dataframe_json: str) -> str:
12811281
"""Returns a self-contained HTML for displaying inference results."""
12821282
payload_b64 = _encode_to_base64(dataframe_json)
12831283
return textwrap.dedent(
@@ -1475,7 +1475,7 @@ def display_evaluation_result(
14751475
summary.update(win_rates[summary["metric_name"]])
14761476

14771477
result_dump["metadata"] = metadata_payload
1478-
html_content = _get_comparison_html(json.dumps(result_dump))
1478+
html_content = get_comparison_html(json.dumps(result_dump))
14791479
else:
14801480
single_dataset = input_dataset_list[0] if input_dataset_list else None
14811481
processed_rows = []
@@ -1499,7 +1499,7 @@ def display_evaluation_result(
14991499
cand_res["raw_json"] = original_case["response_raw_json"]
15001500

15011501
result_dump["metadata"] = metadata_payload
1502-
html_content = _get_evaluation_html(json.dumps(result_dump))
1502+
html_content = get_evaluation_html(json.dumps(result_dump))
15031503

15041504
display.display(display.HTML(html_content))
15051505

@@ -1553,11 +1553,11 @@ def display_evaluation_dataset(eval_dataset_obj: types.EvaluationDataset) -> Non
15531553
processed_rows.append(processed_row)
15541554

15551555
dataframe_json_string = json.dumps(processed_rows, ensure_ascii=False, default=str)
1556-
html_content = _get_inference_html(dataframe_json_string)
1556+
html_content = get_inference_html(dataframe_json_string)
15571557
display.display(display.HTML(html_content))
15581558

15591559

1560-
def _get_loss_analysis_html(loss_analysis_json: str) -> str:
1560+
def get_loss_analysis_html(loss_analysis_json: str) -> str:
15611561
"""Returns self-contained HTML for loss pattern analysis visualization."""
15621562
payload_b64 = _encode_to_base64(loss_analysis_json)
15631563
return textwrap.dedent(
@@ -1865,7 +1865,7 @@ def display_loss_clusters_response(
18651865
)
18661866
raise
18671867

1868-
html_content = _get_loss_analysis_html(
1868+
html_content = get_loss_analysis_html(
18691869
json.dumps(result_dump, ensure_ascii=False, default=_pydantic_serializer)
18701870
)
18711871
display.display(display.HTML(html_content))
@@ -1892,7 +1892,7 @@ def display_loss_analysis_result(
18921892
)
18931893
raise
18941894

1895-
html_content = _get_loss_analysis_html(
1895+
html_content = get_loss_analysis_html(
18961896
json.dumps(wrapped, ensure_ascii=False, default=_pydantic_serializer)
18971897
)
18981898
display.display(display.HTML(html_content))
@@ -1968,7 +1968,7 @@ def display_loss_analysis_results(
19681968
19691969
Wraps the list of LossAnalysisResult objects into the same JSON
19701970
structure used by GenerateLossClustersResponse and renders using
1971-
the shared _get_loss_analysis_html() function.
1971+
the shared get_loss_analysis_html() function.
19721972
19731973
When ``eval_item_map`` is provided (from
19741974
``get_evaluation_run(include_evaluation_items=True)``), the examples
@@ -1997,7 +1997,7 @@ def display_loss_analysis_results(
19971997
)
19981998
raise
19991999

2000-
html_content = _get_loss_analysis_html(
2000+
html_content = get_loss_analysis_html(
20012001
json.dumps(wrapped, ensure_ascii=False, default=_pydantic_serializer)
20022002
)
20032003
display.display(display.HTML(html_content))
@@ -2015,3 +2015,12 @@ def display_evaluation_run_status(eval_run_obj: "types.EvaluationRun") -> None:
20152015
error_message = str(eval_run_obj.error) if eval_run_obj.error else None
20162016
html_content = _get_status_html(status, error_message)
20172017
display.display(display.HTML(html_content))
2018+
2019+
2020+
# Backward-compatible private aliases for the public HTML generators.
2021+
# These are kept temporarily to avoid breaking existing callers that depend on
2022+
# the previous private names. New code should use the public names above.
2023+
_get_evaluation_html = get_evaluation_html
2024+
_get_comparison_html = get_comparison_html
2025+
_get_inference_html = get_inference_html
2026+
_get_loss_analysis_html = get_loss_analysis_html

0 commit comments

Comments
 (0)