Skip to content

Commit 301eff3

Browse files
authored
feat: Add required_metrics to CodeMetric (#435)
1 parent 7cb4a80 commit 301eff3

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/galileo/__future__/metric.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,13 +779,15 @@ class CodeMetric(Metric):
779779
# Type annotations for code-specific attributes
780780
node_level: StepType | None
781781
code: str | None
782+
required_metrics: list[str] | None
782783

783784
def __init__(
784785
self,
785786
name: str,
786787
*,
787788
code: str | None = None,
788789
node_level: StepType | None = None,
790+
required_metrics: list[str] | None = None,
789791
description: str = "",
790792
tags: list[str] | None = None,
791793
version: int | None = None,
@@ -797,6 +799,7 @@ def __init__(
797799
name: The name of the metric.
798800
code: The Python code for the scorer (optional, can be set later or loaded from file).
799801
node_level: Node level for the metric. Defaults to StepType.llm.
802+
required_metrics: List of metric names that this code metric depends on.
800803
description: Description of the metric.
801804
tags: Tags associated with the metric.
802805
version: Specific version to reference (for existing metrics).
@@ -805,6 +808,7 @@ def __init__(
805808

806809
self.code = code
807810
self.node_level = node_level or StepType.llm
811+
self.required_metrics = required_metrics
808812
self.scorer_type = ScorerTypes.CODE
809813

810814
def load_code(self, code_file_path: str) -> CodeMetric:
@@ -864,7 +868,7 @@ def _validate_code(self, config: GalileoPythonConfig) -> str:
864868
code_bytes = self.code.encode("utf-8")
865869
code_file = File(payload=code_bytes, file_name="scorer.py")
866870
validate_body = BodyValidateCodeScorerScorersCodeValidatePost(
867-
file=code_file, scoreable_node_types=[self.node_level.value]
871+
file=code_file, scoreable_node_types=[self.node_level.value], required_scorers=self.required_metrics
868872
)
869873

870874
validate_response = validate_code_scorer_scorers_code_validate_post.sync(
@@ -926,6 +930,7 @@ def _validate_code(self, config: GalileoPythonConfig) -> str:
926930
CODE_VALIDATION_INITIAL_DELAY * (CODE_VALIDATION_BACKOFF_MULTIPLIER**attempt),
927931
CODE_VALIDATION_MAX_DELAY,
928932
)
933+
929934
logger.debug(
930935
f"CodeMetric._validate_code: task_id='{task_id}' - pending "
931936
f"(elapsed: {elapsed:.1f}s/{CODE_VALIDATION_TIMEOUT:.0f}s, next delay: {delay:.2f}s)"
@@ -995,6 +1000,7 @@ def create(self) -> CodeMetric:
9951000
description=self.description,
9961001
tags=self.tags,
9971002
scoreable_node_types=[self.node_level.value],
1003+
required_scorers=self.required_metrics,
9981004
)
9991005

10001006
scorer_response = create_scorers_post.sync(client=config.api_client, body=scorer_request)

tests/future/test_metric.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,16 @@ def test_init_with_all_fields(self, reset_configuration: None) -> None:
632632
assert metric.description == "Test code metric description"
633633
assert metric.tags == ["test", "code"]
634634

635+
def test_init_with_required_metrics(self, reset_configuration: None) -> None:
636+
"""Test initializing a CodeMetric with required_metrics."""
637+
metric = CodeMetric(
638+
name="Test Code Metric", node_level=StepType.llm, required_metrics=["context_adherence", "completeness"]
639+
)
640+
641+
assert metric.name == "Test Code Metric"
642+
assert metric.required_metrics == ["context_adherence", "completeness"]
643+
assert metric.node_level == StepType.llm
644+
635645

636646
class TestCodeMetricCreate:
637647
"""Test suite for CodeMetric.create() method."""
@@ -862,6 +872,73 @@ def test_create_with_different_node_levels(
862872
# Verify the node_level is set on the metric itself
863873
assert metric.node_level == node_level
864874

875+
@patch("galileo.__future__.metric.GalileoPythonConfig.get")
876+
@patch("galileo.__future__.metric.get_validate_code_scorer_task_result_scorers_code_validate_task_id_get")
877+
@patch("galileo.__future__.metric.validate_code_scorer_scorers_code_validate_post")
878+
@patch("galileo.__future__.metric.create_code_scorer_version_scorers_scorer_id_version_code_post")
879+
@patch("galileo.__future__.metric.create_scorers_post")
880+
@patch("galileo.__future__.metric.Scorers")
881+
def test_create_with_required_metrics(
882+
self,
883+
mock_scorers_class: MagicMock,
884+
mock_create_scorers: MagicMock,
885+
mock_create_version: MagicMock,
886+
mock_validate_post: MagicMock,
887+
mock_validate_get: MagicMock,
888+
mock_config: MagicMock,
889+
reset_configuration: None,
890+
create_temp_code_file,
891+
mock_api_client,
892+
mock_scorer_response,
893+
mock_version_response,
894+
mock_scorer_full,
895+
mock_validation_response,
896+
mock_validation_task_result,
897+
) -> None:
898+
"""Test create() passes required_metrics to validation and scorer creation APIs."""
899+
# Mock the config
900+
mock_config.return_value.api_client = mock_api_client
901+
902+
# Create a temporary Python file
903+
code_file = create_temp_code_file()
904+
905+
# Mock the validation flow
906+
task_id = str(uuid4())
907+
mock_validate_post.sync.return_value = mock_validation_response(task_id)
908+
mock_validate_get.sync.return_value = mock_validation_task_result(TaskResultStatus.COMPLETED)
909+
910+
# Mock the scorer creation response
911+
scorer_id = str(uuid4())
912+
mock_create_scorers.sync.return_value = mock_scorer_response(scorer_id, "Test Code Metric")
913+
914+
# Mock the version creation response
915+
mock_create_version.sync.return_value = mock_version_response(scorer_id)
916+
917+
# Mock the scorer list response for refresh
918+
mock_scorers_service = MagicMock()
919+
mock_scorers_service.list.return_value = [mock_scorer_full(scorer_id, "Test Code Metric")]
920+
mock_scorers_class.return_value = mock_scorers_service
921+
922+
required_metrics = ["context_adherence", "completeness"]
923+
924+
# Create the metric with required_metrics
925+
metric = (
926+
CodeMetric(name="Test Code Metric", node_level=StepType.llm, required_metrics=required_metrics)
927+
.load_code(str(code_file))
928+
.create()
929+
)
930+
931+
# Verify the metric was created successfully
932+
assert metric.is_synced()
933+
934+
# Verify required_scorers was passed to validation API
935+
validate_call = mock_validate_post.sync.call_args
936+
assert validate_call.kwargs["body"].required_scorers == required_metrics
937+
938+
# Verify required_scorers was passed to scorer creation API
939+
create_scorer_call = mock_create_scorers.sync.call_args
940+
assert create_scorer_call.kwargs["body"].required_scorers == required_metrics
941+
865942
@patch("galileo.__future__.metric.GalileoPythonConfig.get")
866943
def test_create_reads_code_file_correctly(
867944
self,

0 commit comments

Comments
 (0)