Conversation
…ing to default model id
|
@strandly-the-agent review the changes carefully |
strandly-the-agent
left a comment
There was a problem hiding this comment.
Approve — the fix is correct and minimal; needs a rebase before merge (conflicts with main).
Reproduced the bug on main (a default-model OutputEvaluator round-trips as model='global.anthropic.claude-sonnet-4-6') and confirmed the branch restores model is None. Old saved files that already contain model_id still load unchanged; only newly written files drop the key. No other consumer depends on the key being present.
Action: rebase — tests/strands_evals/test_experiment.py conflicts with main on the import block (this PR removes the DEFAULT_BEDROCK_MODEL_ID import; #330 added SkillSelectionScore / _get_label_from_score imports right next to it). Resolving as "keep main's new imports, drop DEFAULT_BEDROCK_MODEL_ID" is all it takes; the full suite passes on that resolution.
Question (non-blocking, for poshinchen as #40 author): #40 deliberately wrote the default out as model_id, presumably so a saved experiment records which judge ran. This PR trades that provenance for "follow the current default". I think the trade is right — evaluators build the judge via Agent(model=self.model) (output_evaluator.py:133), so None is resolved by the SDK's default, not by evals' own DEFAULT_BEDROCK_MODEL_ID; pinning to the evals constant was never guaranteed to match what actually ran. If provenance matters, it belongs in the run report, not in the experiment definition. Worth a one-line confirmation that this reversal is intended.
Verification, appendix
✅ Verified (PR head 9162a99 rebased onto main abf7b91, conflict resolved as above):
- Repro (
Experiment.to_dict()→from_dict()withOutputEvaluator(rubric="r")):main→{'evaluator_type': 'OutputEvaluator', 'rubric': 'r', 'model_id': 'global.anthropic.claude-sonnet-4-6'}, reloadedmodel='global.anthropic.claude-sonnet-4-6'; PR →{'evaluator_type': 'OutputEvaluator', 'rubric': 'r'}, reloadedmodel=None. pytest tests/strands_evals/evaluators/test_evaluator.py tests/strands_evals/test_experiment.py→ 152 passed.- Full
pytest tests/→ 2083 passed; 9 failed / 49 errors are alllangfuse_provider/telemetry/test_config/cli/test_fetchfailing on missing optional deps (langfuse,opentelemetry.exporter) — identical onmain, unrelated. - Consumers of
model_id: onlyexperiment.py:836(guardedif "model_id" in evaluator_args) and a guarded test helper. Redteam serializes its model via its own_serialize_modelpath (already omitsNone) — unaffected. - All 25+ evaluator subclasses default
model=Noneand store the arg verbatim (self.model = model), so the newelif v is None: passis behaviour-identical to the old branch in every case except the targeted one. - New tests use real objects, and
test_none_model_survives_round_trip_as_nonefails on old code — a genuine regression test.
Reading order: evaluator.py hunk → test_evaluator.py::test_none_model_survives_round_trip_as_none → test_experiment.py expectation updates.
Appendix — non-blocking (1)
- ⚪
_get_model_id'sNone → DEFAULT_BEDROCK_MODEL_IDbranch (evaluator.py:75-76) is now unreachable fromsrc/(its only caller passes aModel); kept alive bytest_evaluator.py:103-107. Optional cleanup, not for this PR.
What
Evaluator.to_dict()serialized a defaultmodel=Noneasmodel_id=DEFAULT_BEDROCK_MODEL_ID. Combined withExperiment.from_dict()restoringmodel_idas an explicitmodel=, every judge that relied on the runtime default got pinned to whateverDEFAULT_BEDROCK_MODEL_IDhappened to be at serialization time, silently, on everyto_file/from_fileround-trip.This changes
to_dict()to omitmodelentirely when it isNone— the same way every other default-valued field is omitted — so a reloaded evaluator restoresmodel=Noneand re-resolves the default at runtime.Why it matters
model is None) is how the library expresses "use the current default judge." Persisting it as a concrete ID means upgradingDEFAULT_BEDROCK_MODEL_IDin a future release no longer takes effect for any saved experiment.save/loadis affected.Change
_get_model_id(None)still returnsDEFAULT_BEDROCK_MODEL_ID(unchanged) — this only affects serialization of the default.Tests
test_to_dict_omits_default_none_model— amodel=Noneevaluator serializes with nomodel/model_idkey.test_none_model_survives_round_trip_as_none—Experiment.to_dict()→from_dict()restoresmodel is None.to_dictassertions (Output/Trajectory/Interactions defaults) that were pinning the buggymodel_idoutput.Full suite green; ruff clean.