Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions pyrit/score/scorer_evaluation/human_labeled_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from pyrit.score.scorer_evaluation.metrics_type import MetricsType

if TYPE_CHECKING:
from collections.abc import Hashable

from pyrit.models.harm_definition import HarmDefinition

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -271,11 +273,21 @@ def from_csv(
if not harm_definition_version and parsed_harm_definition_version:
harm_definition_version = parsed_harm_definition_version

# Preserve text before pandas can infer numeric or boolean values.
text_dtypes: dict[Hashable, type[str]] = dict.fromkeys(
(
cls.STANDARD_ASSISTANT_RESPONSE_COL,
cls.STANDARD_OBJECTIVE_COL,
cls.STANDARD_HARM_COL,
cls.STANDARD_DATA_TYPE_COL,
),
str,
)
# Try UTF-8 first, fall back to latin-1 for files with special characters
try:
eval_df = pd.read_csv(csv_path, comment="#", encoding="utf-8")
eval_df = pd.read_csv(csv_path, comment="#", encoding="utf-8", dtype=text_dtypes)
except UnicodeDecodeError:
eval_df = pd.read_csv(csv_path, comment="#", encoding="latin-1")
eval_df = pd.read_csv(csv_path, comment="#", encoding="latin-1", dtype=text_dtypes)

# Drop rows where every column is NaN (e.g. trailing blank lines in the CSV)
eval_df = eval_df.dropna(how="all")
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/score/test_human_labeled_dataset.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from pathlib import Path

import pytest

from pyrit.common.path import SCORER_EVALS_PATH
Expand All @@ -13,6 +15,35 @@
)


@pytest.mark.parametrize("value", ["00123", "1e3", "1.2300", "True"])
@pytest.mark.parametrize("encoding", ["utf-8", "latin-1"])
@pytest.mark.parametrize("metrics_type", [MetricsType.OBJECTIVE, MetricsType.HARM])
def test_from_csv_preserves_numeric_and_boolean_text(
tmp_path: Path, value: str, encoding: str, metrics_type: MetricsType
) -> None:
column = "objective" if metrics_type == MetricsType.OBJECTIVE else "harm_category"
label = "1" if metrics_type == MetricsType.OBJECTIVE else "0.5"
csv_path = tmp_path / "text.csv"
csv_path.write_text(
f"assistant_response,{column},human_score,notes\n{value},00456,{label},café\n",
encoding=encoding,
)

dataset = HumanLabeledDataset.from_csv(csv_path=csv_path, metrics_type=metrics_type, version="1.0")
entry = dataset.entries[0]
piece = entry.conversation[0].message_pieces[0]

assert piece.original_value == value
assert piece.converted_value == value
if isinstance(entry, ObjectiveHumanLabeledEntry):
assert entry.objective == "00456"
assert entry.human_scores == [True]
else:
assert isinstance(entry, HarmHumanLabeledEntry)
assert entry.harm_category == "00456"
assert entry.human_scores == [0.5]


@pytest.fixture
def sample_messages():
return [
Expand Down
Loading