"""Problem-layer adapter utilities for orchestration-owned packet handling."""
from __future__ import annotations
import importlib
import random
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass, field, fields, is_dataclass
from typing import Any, cast
from ..schemas import ValidationError
[docs]
@dataclass(slots=True)
class ProblemPacket:
"""Normalized executable problem payload."""
problem_id: str
family: str
brief: str
payload: dict[str, Any] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
evaluator: Callable[[Mapping[str, Any]], Any] | None = None
[docs]
def resolve_problem(
problem_spec_ref: Any,
*,
registry: Mapping[str, ProblemPacket] | None = None,
) -> ProblemPacket:
"""Resolve a problem reference into a normalized packet."""
if isinstance(problem_spec_ref, ProblemPacket):
return problem_spec_ref
if isinstance(problem_spec_ref, str) and registry and problem_spec_ref in registry:
return _packet_from_registry_entry(registry[problem_spec_ref])
if isinstance(problem_spec_ref, Mapping):
return _packet_from_mapping(problem_spec_ref)
if isinstance(problem_spec_ref, str):
owner_integration = _load_problems_integration_module()
if owner_integration is None:
raise ValidationError(
"String problem references now require `design_research_problems.integration`. "
"Install the coordinated monthly release or pass an explicit `ProblemPacket` "
"through `problem_registry`."
)
binding = owner_integration.resolve_problem_binding(problem_spec_ref)
return _packet_from_problem_binding(binding, owner_integration=owner_integration)
raise ValidationError(
"Standalone design-research-experiments accepts only `ProblemPacket` instances, "
"explicit packet mappings, or string problem ids resolved through "
"`design_research_problems.integration`."
)
def evaluate_problem(
packet: ProblemPacket,
run_output: Mapping[str, Any],
) -> list[dict[str, Any]]:
"""Execute family-specific evaluation when available and normalize rows."""
owner_rows = _evaluate_owner_problem(packet, run_output)
if owner_rows is not None:
return owner_rows
if packet.evaluator is None:
return []
evaluator_input = _resolve_evaluator_input(packet, run_output)
raw = packet.evaluator(evaluator_input)
return _normalize_evaluation_payload(raw)
def sample_problem_packets(
problem_refs: Sequence[Any],
*,
registry: Mapping[str, ProblemPacket] | None = None,
sample_size: int | None = None,
seed: int = 0,
balanced_by_family: bool = False,
) -> list[ProblemPacket]:
"""Resolve and sample problem packets with optional family balancing."""
resolved = [resolve_problem(problem_ref, registry=registry) for problem_ref in problem_refs]
if sample_size is None or sample_size >= len(resolved):
return resolved
if not balanced_by_family:
randomizer = random.Random(seed)
return randomizer.sample(resolved, sample_size)
buckets: dict[str, list[ProblemPacket]] = {}
for packet in resolved:
buckets.setdefault(packet.family, []).append(packet)
randomizer = random.Random(seed)
for bucket in buckets.values():
randomizer.shuffle(bucket)
sampled: list[ProblemPacket] = []
families = sorted(buckets)
while families and len(sampled) < sample_size:
next_families: list[str] = []
for family in families:
bucket = buckets[family]
if not bucket:
continue
sampled.append(bucket.pop())
if len(sampled) >= sample_size:
break
if bucket:
next_families.append(family)
families = next_families
return sampled
def _packet_from_registry_entry(problem_ref: Any) -> ProblemPacket:
"""Resolve one explicit registry entry into a packet."""
if isinstance(problem_ref, ProblemPacket):
return problem_ref
if isinstance(problem_ref, Mapping):
return _packet_from_mapping(problem_ref)
raise ValidationError(
"Problem registries now require `ProblemPacket` values or explicit packet "
"mappings. Resolve packaged sibling problems by string id through "
"`design_research_problems.integration`."
)
def _packet_from_mapping(problem_spec_ref: Mapping[str, Any]) -> ProblemPacket:
"""Build one packet from an explicit mapping payload."""
return ProblemPacket(
problem_id=str(problem_spec_ref.get("problem_id", "problem")),
family=str(problem_spec_ref.get("family", "unknown")),
brief=str(problem_spec_ref.get("brief", "")),
payload=dict(cast(Mapping[str, Any], problem_spec_ref.get("payload", {}))),
metadata=dict(cast(Mapping[str, Any], problem_spec_ref.get("metadata", {}))),
evaluator=cast(
Callable[[Mapping[str, Any]], Any] | None,
problem_spec_ref.get("evaluator"),
),
)
def _packet_from_problem_binding(binding: Any, *, owner_integration: Any) -> ProblemPacket:
"""Convert one owner-owned `ProblemBinding` into the experiments packet shape."""
return ProblemPacket(
problem_id=str(binding.problem_id),
family=str(binding.family),
brief=str(binding.brief),
payload={
"problem_object": binding.problem_object,
"_owner_problem_binding": binding,
"_owner_problem_integration": owner_integration,
},
metadata=dict(binding.metadata),
)
def _load_problems_integration_module() -> Any | None:
"""Return the packaged problem-integration module when available."""
try:
return importlib.import_module("design_research_problems.integration")
except ImportError as exc:
try:
importlib.import_module("design_research_problems")
except ImportError:
return None
raise ValidationError(
"design-research-problems is installed but does not expose the package-owned "
"`integration` module. Upgrade to the coordinated monthly release."
) from exc
def _resolve_evaluator_input(_packet: ProblemPacket, run_output: Mapping[str, Any]) -> Any:
"""Resolve the best evaluator input for packaged and external problem evaluators."""
preferred_keys = ("candidate", "state", "answer", "solution", "final_answer", "x")
for key in preferred_keys:
if key in run_output:
return run_output[key]
return run_output
def _evaluate_owner_problem(
packet: ProblemPacket,
run_output: Mapping[str, Any],
) -> list[dict[str, Any]] | None:
"""Delegate packaged-problem evaluation to the owning problem library."""
owner_integration = packet.payload.get("_owner_problem_integration")
binding = packet.payload.get("_owner_problem_binding")
if owner_integration is None or binding is None:
return None
evaluate = getattr(owner_integration, "evaluate_problem_output", None)
if not callable(evaluate):
raise ValidationError(
"design-research-problems is installed but does not expose "
"`evaluate_problem_output(...)`. Upgrade to the coordinated monthly release."
)
rows = evaluate(binding, run_output)
if not isinstance(rows, Sequence) or isinstance(rows, (str, bytes)):
raise ValidationError("Packaged problem evaluation must return a sequence of rows.")
return [dict(cast(Mapping[str, Any], row)) for row in rows if isinstance(row, Mapping)]
def _normalize_evaluation_payload(raw: Any) -> list[dict[str, Any]]:
"""Normalize evaluator payloads into canonical experiment evaluation rows."""
if isinstance(raw, Mapping):
if _looks_like_evaluation_row(raw):
return [_normalize_evaluation_row(raw)]
return _metric_rows_from_mapping(raw)
if isinstance(raw, Sequence) and not isinstance(raw, (str, bytes)):
rows: list[dict[str, Any]] = []
for row in raw:
rows.extend(_normalize_evaluation_payload(row))
return rows
mapping = _object_to_mapping(raw)
if mapping is None:
return []
return _metric_rows_from_mapping(mapping)
def _looks_like_evaluation_row(row: Mapping[str, Any]) -> bool:
"""Return whether a mapping already resembles one canonical evaluation row."""
return any(key in row for key in ("metric_name", "metric_value", "value"))
def _metric_rows_from_mapping(metrics: Mapping[str, Any]) -> list[dict[str, Any]]:
"""Expand a metrics mapping into canonical evaluation rows."""
rows: list[dict[str, Any]] = []
for metric_name, metric_value in metrics.items():
if str(metric_name) == "higher_is_better":
continue
if not _is_metric_scalar(metric_value):
continue
rows.append(
{
"evaluator_id": "problem_evaluator",
"metric_name": str(metric_name),
"metric_value": metric_value,
"metric_unit": "unitless",
"aggregation_level": "run",
"notes_json": {},
}
)
return rows
def _object_to_mapping(value: Any) -> Mapping[str, Any] | None:
"""Best-effort conversion of an evaluation object to a flat mapping."""
if value is None:
return None
if isinstance(value, Mapping):
return value
if is_dataclass(value) and not isinstance(value, type):
return {field_info.name: getattr(value, field_info.name) for field_info in fields(value)}
to_dict = getattr(value, "to_dict", None)
if callable(to_dict):
try:
candidate = to_dict()
except Exception:
candidate = None
if isinstance(candidate, Mapping):
return cast(Mapping[str, Any], candidate)
if hasattr(value, "__dict__"):
return cast(Mapping[str, Any], vars(value))
return None
def _normalize_evaluation_row(row: Mapping[str, Any]) -> dict[str, Any]:
"""Normalize one evaluator row to canonical shape."""
return {
"evaluator_id": str(row.get("evaluator_id", "problem_evaluator")),
"metric_name": str(row.get("metric_name", "score")),
"metric_value": row.get("metric_value", row.get("value")),
"metric_unit": str(row.get("metric_unit", "unitless")),
"aggregation_level": str(row.get("aggregation_level", "run")),
"notes_json": row.get("notes_json", {}),
}
def _is_metric_scalar(value: Any) -> bool:
"""Return whether one value is suitable for scalar metric export."""
return isinstance(value, bool | int | float)