@@ -520,7 +520,79 @@ def test_response_structure(self):
520520 assert result .clusters [0 ].item_count == 3
521521 assert result .clusters [1 ].cluster_id == "cluster-2"
522522
523- def test_response_show_with_results (self , capsys ):
523+ def test_get_loss_analysis_html (self ):
524+ """Tests that _get_loss_analysis_html generates valid HTML with data."""
525+ from vertexai ._genai import _evals_visualization
526+ import json
527+
528+ data = {
529+ "results" : [
530+ {
531+ "config" : {
532+ "metric" : "test_metric" ,
533+ "candidate" : "test-candidate" ,
534+ },
535+ "clusters" : [
536+ {
537+ "cluster_id" : "c1" ,
538+ "taxonomy_entry" : {
539+ "l1_category" : "Tool Calling" ,
540+ "l2_category" : "Missing Invocation" ,
541+ "description" : "Agent failed to call the tool." ,
542+ },
543+ "item_count" : 5 ,
544+ "examples" : [
545+ {
546+ "evaluation_result" : {
547+ "request" : {
548+ "prompt" : {
549+ "agent_data" : {
550+ "turns" : [
551+ {
552+ "turn_index" : 0 ,
553+ "events" : [
554+ {
555+ "author" : "user" ,
556+ "content" : {
557+ "parts" : [
558+ {
559+ "text" : "Find flights to Paris"
560+ }
561+ ],
562+ },
563+ }
564+ ],
565+ }
566+ ],
567+ },
568+ },
569+ },
570+ },
571+ "failed_rubrics" : [
572+ {
573+ "rubric_id" : "tool_use" ,
574+ "classification_rationale" : "Did not invoke find_flights." ,
575+ }
576+ ],
577+ }
578+ ],
579+ },
580+ ],
581+ }
582+ ]
583+ }
584+ html = _evals_visualization ._get_loss_analysis_html (json .dumps (data ))
585+ assert "Loss Pattern Analysis" in html
586+ assert "test_metric" not in html # data is Base64-encoded in the HTML
587+ assert "<!DOCTYPE html>" in html
588+ assert "extractScenarioPreview" in html
589+ assert "example-scenario" in html
590+
591+ def test_display_loss_clusters_response_no_ipython (self ):
592+ """Tests graceful fallback when not in IPython."""
593+ from vertexai ._genai import _evals_visualization
594+ from unittest import mock
595+
524596 response = common_types .GenerateLossClustersResponse (
525597 results = [
526598 common_types .LossAnalysisResult (
@@ -541,12 +613,17 @@ def test_response_show_with_results(self, capsys):
541613 )
542614 ],
543615 )
544- response .show ()
545- captured = capsys .readouterr ()
546- assert "test_metric" in captured .out
547- assert "c1" in captured .out
616+ with mock .patch .object (
617+ _evals_visualization , "_is_ipython_env" , return_value = False
618+ ):
619+ # Should not raise, just log a warning
620+ response .show ()
621+
622+ def test_display_loss_analysis_result_no_ipython (self ):
623+ """Tests graceful fallback for individual result when not in IPython."""
624+ from vertexai ._genai import _evals_visualization
625+ from unittest import mock
548626
549- def test_loss_analysis_result_show (self , capsys ):
550627 result = common_types .LossAnalysisResult (
551628 config = common_types .LossAnalysisConfig (
552629 metric = "test_metric" ,
@@ -563,10 +640,10 @@ def test_loss_analysis_result_show(self, capsys):
563640 ),
564641 ],
565642 )
566- result . show ()
567- captured = capsys . readouterr ()
568- assert "test_metric" in captured . out
569- assert "c1" in captured . out
643+ with mock . patch . object (
644+ _evals_visualization , "_is_ipython_env" , return_value = False
645+ ):
646+ result . show ()
570647
571648
572649def _make_eval_result (
0 commit comments