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
13 changes: 11 additions & 2 deletions poweragentbench/benchmark_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,15 @@ def load_solution(path: Path) -> dict[str, float]:
value = item.get("value")
if not isinstance(action_id, str):
raise ValueError("Each action entry must include a string 'id'.")
if not isinstance(value, (int, float)):
# bool subclasses int in Python, but JSON booleans are not numeric action values.
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"Action '{action_id}' must include a numeric 'value'.")
numeric_value = float(value)
if not math.isfinite(numeric_value):
raise ValueError(f"Action '{action_id}' must include a finite numeric 'value'.")
if action_id in values:
raise ValueError(f"Duplicate action '{action_id}' in solution file.")
values[action_id] = float(value)
values[action_id] = numeric_value

return values

Expand All @@ -144,6 +148,11 @@ def validate_action_values(
for action_id, value in action_values.items():
if action_id not in actionspace["action_index"]:
raise ValueError(f"Unknown action '{action_id}'.")
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"Action '{action_id}' must be a numeric value.")
value = float(value)
if not math.isfinite(value):
raise ValueError(f"Action '{action_id}' must be a finite numeric value.")
action = actionspace["action_index"][action_id]
min_value = float(action["min_value"])
max_value = float(action["max_value"])
Expand Down
26 changes: 26 additions & 0 deletions tests/test_benchmark_utils_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,29 @@ def test_solution_loader_rejects_non_numeric_values(tmp_path: Path) -> None:

with pytest.raises(ValueError, match="numeric"):
load_solution(path)



def test_solution_loader_rejects_boolean_values(tmp_path: Path) -> None:
path = tmp_path / "boolean_value.json"
path.write_text(json.dumps({"actions": [{"id": "tap_T1_up", "value": True}]}), encoding="utf-8")

with pytest.raises(ValueError, match="numeric"):
load_solution(path)


@pytest.mark.parametrize("value", [float("nan"), float("inf"), float("-inf")])
def test_solution_loader_rejects_non_finite_values(tmp_path: Path, value: float) -> None:
path = tmp_path / "non_finite.json"
path.write_text(json.dumps({"actions": [{"id": "tap_T1_up", "value": value}]}), encoding="utf-8")

with pytest.raises(ValueError, match="finite"):
load_solution(path)


@pytest.mark.parametrize("value", [True, float("nan"), float("inf"), float("-inf")])
def test_validate_action_values_rejects_boolean_and_non_finite_values(value: float) -> None:
actionspace = load_actionspace(LEVEL1 / "actionspace.json")

with pytest.raises(ValueError, match="numeric|finite"):
validate_action_values({"tap_T1_up": value}, actionspace)
Loading