Skip to content

Commit 38c8dd4

Browse files
committed
More useful return values from runways.
1 parent bcf249e commit 38c8dd4

5 files changed

Lines changed: 90 additions & 33 deletions

File tree

‎src/modelplane/runways/annotator.py‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@
2727
is_debug_mode,
2828
setup_annotator_credentials,
2929
)
30-
from modelplane.runways.data import BaseInput, build_and_log_input
30+
from modelplane.runways.data import (
31+
Artifact,
32+
BaseInput,
33+
RunArtifacts,
34+
build_and_log_input,
35+
)
3136

3237
KNOWN_ENSEMBLES: Dict[str, AnnotatorSet] = {}
3338
# try to load the private ensemble
@@ -55,7 +60,7 @@ def annotate(
5560
prompt_text_col=None,
5661
sut_uid_col=None,
5762
sut_response_col=None,
58-
) -> str:
63+
) -> RunArtifacts:
5964
"""
6065
Run annotations and record measurements.
6166
"""
@@ -138,7 +143,15 @@ def annotate(
138143
/ pipeline_runner.output_file_name,
139144
dir=tmp,
140145
)
141-
return run.info.run_id
146+
artifacts = {
147+
input_data.local_path().name: input_data.artifact,
148+
pipeline_runner.output_file_name: Artifact(
149+
experiment_id=run.info.experiment_id,
150+
run_id=run.info.run_id,
151+
name=pipeline_runner.output_file_name,
152+
),
153+
}
154+
return RunArtifacts(run_id=run.info.run_id, artifacts=artifacts)
142155

143156

144157
def _get_annotator_settings(

‎src/modelplane/runways/data.py‎

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from dataclasses import dataclass
12
import os
23
import shutil
34
from abc import ABC, abstractmethod
@@ -14,13 +15,39 @@
1415
)
1516

1617

18+
class Artifact:
19+
20+
def __init__(self, experiment_id: str, run_id: str, name: str):
21+
self.name = name
22+
tracking_uri = mlflow.get_tracking_uri()
23+
self._mlflow_link = (
24+
f"{tracking_uri}/#/experiments/{experiment_id}/runs/{run_id}"
25+
)
26+
self._download_link = f"{tracking_uri}/api/2.0/mlflow/artifacts/download?run_id={run_id}&artifact_path={name}"
27+
28+
@property
29+
def mlflow_link(self) -> str:
30+
return self._mlflow_link
31+
32+
@property
33+
def download_link(self) -> str:
34+
return self._download_link
35+
36+
37+
@dataclass
38+
class RunArtifacts:
39+
run_id: str
40+
artifacts: dict[str, Artifact | None]
41+
42+
1743
class BaseInput(ABC):
1844
"""Base class for input datasets."""
1945

2046
input_type: str
2147

2248
def __init__(self):
2349
self.input_run_id = None
50+
self._artifact = None
2451

2552
def __init_subclass__(cls):
2653
super().__init_subclass__()
@@ -39,17 +66,15 @@ def log_artifact(self):
3966
local = self.local_path()
4067
mlflow.log_artifact(str(local))
4168
mlflow.set_tags(self.input_tags())
42-
mlflow_tracking_uri = mlflow.get_tracking_uri()
43-
self._mlflow_link = f"{mlflow_tracking_uri}/#/experiments/{current_run.info.experiment_id}/runs/{current_run.info.run_id}/artifacts/{local.name}"
44-
self._download_link = f"{mlflow_tracking_uri}/api/2.0/mlflow-artifacts/artifacts/{local.name}?run_id={current_run.info.run_id}"
45-
46-
@property
47-
def mlflow_link(self) -> str:
48-
return self._mlflow_link
69+
self._artifact = Artifact(
70+
experiment_id=current_run.info.experiment_id,
71+
run_id=current_run.info.run_id,
72+
name=local.name,
73+
)
4974

5075
@property
51-
def download_link(self) -> str:
52-
return self._download_link
76+
def artifact(self) -> Artifact | None:
77+
return self._artifact
5378

5479
@abstractmethod
5580
def local_path(self) -> Path:

‎src/modelplane/runways/responder.py‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
is_debug_mode,
1818
setup_sut_credentials,
1919
)
20-
from modelplane.runways.data import BaseInput, build_and_log_input
20+
from modelplane.runways.data import (
21+
Artifact,
22+
BaseInput,
23+
RunArtifacts,
24+
build_and_log_input,
25+
)
2126

2227

2328
def respond(
@@ -30,7 +35,7 @@ def respond(
3035
num_workers: int = 1,
3136
prompt_uid_col=None,
3237
prompt_text_col=None,
33-
) -> str:
38+
) -> RunArtifacts:
3439
secrets = setup_sut_credentials(sut_id)
3540
sut = SUTS.make_instance(uid=sut_id, secrets=secrets)
3641
params = {"num_workers": num_workers}
@@ -68,4 +73,13 @@ def respond(
6873
local_path=pipeline_runner.output_dir()
6974
/ pipeline_runner.output_file_name,
7075
)
71-
return run.info.run_id
76+
artifacts = {
77+
input_data.local_path().name: input_data.artifact,
78+
pipeline_runner.output_file_name: Artifact(
79+
experiment_id=run.info.experiment_id,
80+
run_id=run.info.run_id,
81+
name=pipeline_runner.output_file_name,
82+
),
83+
}
84+
85+
return RunArtifacts(run_id=run.info.run_id, artifacts=artifacts)

‎src/modelplane/runways/scorer.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
RUN_TYPE_TAG_NAME,
1919
get_experiment_id,
2020
)
21-
from modelplane.runways.data import BaseInput, build_and_log_input
21+
from modelplane.runways.data import BaseInput, RunArtifacts, build_and_log_input
2222

2323

2424
def score(
@@ -30,7 +30,7 @@ def score(
3030
sample_uid_col: str | None = None,
3131
annotator_uid_col: str | None = ANNOTATION_SCHEMA.annotator_uid,
3232
annotation_col: str | None = ANNOTATION_SCHEMA.annotation,
33-
):
33+
) -> RunArtifacts:
3434
"""
3535
Score annotations against ground truth.
3636
Annotations are expected to be in JSON format with an "is_safe" field.
@@ -78,6 +78,11 @@ def score(
7878
)
7979
mlflow.log_metric("num_ground_truth_samples", len(ground_truth_data.df))
8080

81+
artifacts = {
82+
annotation_input.local_path().name: annotation_input.artifact,
83+
ground_truth_input.local_path().name: ground_truth_input.artifact,
84+
}
85+
8186
# Score each annotator in the annotation dataframe.
8287
for annotator in annotation_data.annotators:
8388
score = score_annotator(annotator, annotation_data, ground_truth_data)
@@ -92,7 +97,7 @@ def score(
9297
else:
9398
mlflow.log_metric(f"{annotator}_{metric}", score[metric])
9499

95-
return run.info.run_id
100+
return RunArtifacts(run_id=run.info.run_id, artifacts=artifacts)
96101

97102

98103
def score_annotator(annotator: str, annotation_data, ground_truth_data):

‎tests/it/runways/test_e2e.py‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@ def test_e2e():
2222
experiment = "test_experiment_" + time.strftime("%Y%m%d%H%M%S", time.localtime())
2323
num_workers = 1
2424

25-
run_id = check_responder(
25+
run_artifacts = check_responder(
2626
sut_id=sut_id,
2727
prompts=prompts,
2828
experiment=experiment,
2929
disable_cache=True,
3030
num_workers=num_workers,
3131
)
32-
run_id = check_annotator(
33-
response_run_id=run_id,
32+
run_artifacts = check_annotator(
33+
response_run_id=run_artifacts.run_id,
3434
annotator_ids=[TEST_ANNOTATOR_ID],
3535
experiment=experiment,
3636
disable_cache=True,
3737
num_workers=num_workers,
3838
)
3939
check_scorer(
40-
annotation_run_id=run_id,
40+
annotation_run_id=run_artifacts.run_id,
4141
ground_truth=ground_truth,
4242
annotator_id=TEST_ANNOTATOR_ID,
4343
experiment=experiment,
@@ -51,7 +51,7 @@ def check_responder(
5151
disable_cache: bool,
5252
num_workers: int,
5353
):
54-
run_id = respond(
54+
run_artifacts = respond(
5555
sut_id=sut_id,
5656
prompts=prompts,
5757
experiment=experiment,
@@ -62,10 +62,10 @@ def check_responder(
6262
# confirm experiment exists
6363
exp = mlflow.get_experiment_by_name(experiment)
6464
assert exp is not None
65-
assert run_id is not None
65+
assert run_artifacts.run_id is not None
6666

6767
# validate params / tags logged
68-
run = mlflow.get_run(run_id)
68+
run = mlflow.get_run(run_artifacts.run_id)
6969
params = run.data.params
7070
tags = run.data.tags
7171
assert params.get("num_workers") == str(num_workers)
@@ -75,7 +75,7 @@ def check_responder(
7575
with tempfile.TemporaryDirectory() as temp_dir:
7676
# download/validate the prompt responses artifact
7777
responses_file = mlflow.artifacts.download_artifacts(
78-
run_id=run_id,
78+
run_id=run_artifacts.run_id,
7979
artifact_path=PROMPT_RESPONSE_ARTIFACT_NAME,
8080
dst_path=temp_dir,
8181
)
@@ -90,7 +90,7 @@ def check_responder(
9090
assert (
9191
yesno.lower() == expected
9292
), f"Unexpectedly got '{yesno} for prompt '{response['prompt_text']}'"
93-
return run_id
93+
return run_artifacts
9494

9595

9696
def check_annotator(
@@ -101,7 +101,7 @@ def check_annotator(
101101
num_workers: int,
102102
):
103103
# run the annotator
104-
run_id = annotate(
104+
run_artifacts = annotate(
105105
response_run_id=response_run_id,
106106
annotator_ids=annotator_ids,
107107
experiment=experiment,
@@ -113,7 +113,7 @@ def check_annotator(
113113
assert exp is not None
114114

115115
# validate params / tags / metrics logged
116-
run = mlflow.get_run(run_id)
116+
run = mlflow.get_run(run_artifacts.run_id)
117117
params = run.data.params
118118
tags = run.data.tags
119119
metrics = run.data.metrics
@@ -129,12 +129,12 @@ def check_annotator(
129129
), "Expected total_safe to be 5"
130130

131131
# confirm annotations.csv exists
132-
artifacts = mlflow.artifacts.list_artifacts(run_id=run_id)
132+
artifacts = mlflow.artifacts.list_artifacts(run_id=run_artifacts.run_id)
133133
assert any(
134134
artifact.path == "annotations.csv" for artifact in artifacts
135135
), "Expected 'annotations.csv' artifact not found in run"
136136
# TODO: validate annotations.csv
137-
return run_id
137+
return run_artifacts
138138

139139

140140
def check_scorer(
@@ -143,13 +143,13 @@ def check_scorer(
143143
annotator_id: str,
144144
experiment: str,
145145
):
146-
run_id = score(annotation_run_id, experiment, ground_truth)
146+
run_artifacts = score(annotation_run_id, experiment, ground_truth)
147147
# confirm experiment exists
148148
exp = mlflow.get_experiment_by_name(experiment)
149149
assert exp is not None
150150

151151
# validate params / metrics logged
152-
run = mlflow.get_run(run_id)
152+
run = mlflow.get_run(run_artifacts.run_id)
153153
params = run.data.params
154154
metrics = run.data.metrics
155155
assert params.get("annotation_run_id") == annotation_run_id

0 commit comments

Comments
 (0)