fix(datasets): Resolve only recipe-referenced bindings

Eagerly resolving every DEFAULT_BINDINGS entry made rendering fail on
frames whose events a default binding cannot disambiguate, e.g. the
camera-less vqa default on multi-camera frames, even when the recipe
never references that binding. Add TrainingRecipe.referenced_binding_names()
and skip bindings the recipe does not consume.

Split out of #4183 so the data-layer fix lands independently of the
language runtime.

Refs #4183
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Pepijn
2026-08-07 22:26:24 +02:00
parent 22bd7a2f48
commit 853af1c3f6
3 changed files with 42 additions and 1 deletions
+7
View File
@@ -192,6 +192,13 @@ class TrainingRecipe:
if recipe.weight <= 0:
raise ValueError(f"Blend component {name!r} must have a positive weight.")
def referenced_binding_names(self) -> set[str]:
"""Names of every binding referenced by this recipe's message turns."""
names: set[str] = set()
for turn in self.messages or []:
names |= self._referenced_bindings(turn)
return names
def _referenced_bindings(self, turn: MessageTurn) -> set[str]:
"""Return the binding names that ``turn`` references via placeholders or attributes."""
names: set[str] = set()
+7 -1
View File
@@ -290,8 +290,14 @@ def _resolve_bindings(
bindings: dict[str, LanguageRow | str | None] = {
"task": _resolve_task(task, dataset_ctx, persistent=persistent, sample_idx=sample_idx),
}
specs = {**DEFAULT_BINDINGS, **(recipe.bindings or {})}
declared = recipe.bindings or {}
specs = {**DEFAULT_BINDINGS, **declared}
# Only resolve bindings the recipe consumes: an unreferenced default may be
# unresolvable, e.g. the camera-less ``vqa`` default on multi-camera frames.
needed = recipe.referenced_binding_names() | set(declared)
for name, spec in specs.items():
if name not in needed:
continue
bindings[name] = _resolve_spec(spec, persistent=persistent, events=events, t=t)
return bindings
+28
View File
@@ -197,6 +197,34 @@ def test_emitted_at_filters_vqa_by_camera():
assert wrist["content"] == '{"count": 1}'
def test_unreferenced_default_bindings_are_not_resolved():
# A recipe that never references ``vqa`` must render on frames carrying
# multi-camera VQA events, which the camera-less default ``vqa`` binding
# cannot disambiguate (regression: eager DEFAULT_BINDINGS resolution).
recipe = TrainingRecipe(
messages=[
MessageTurn(role="user", content="${task}", stream="low_level"),
MessageTurn(
role="assistant",
content="${subtask}",
stream="low_level",
target=True,
if_present="subtask",
),
]
)
rendered = render_sample(
recipe=recipe,
persistent=PERSISTENT,
events=EVENTS_AT_3_TWO_CAMERAS,
t=3.0,
sample_idx=0,
task="tidy the table",
)
assert rendered is not None
assert rendered["messages"][1]["content"] == "subtask 1"
def test_emitted_at_raises_on_ambiguous_per_camera_vqa():
with pytest.raises(ValueError, match="Ambiguous resolver"):
emitted_at(