Skip to content

Commit e8bcbf0

Browse files
committed
fix(dspy): keep fields when copying an Example with no visible fields
Example.__init__ guarded the base branch with `if base and ...`. Since __len__ skips dspy_* keys, an Example that only holds dspy_* metadata or an input/label split (e.g. `Example().with_inputs("question")`) is falsy, so copy(), without() and Example(base=...) silently dropped its store and input keys. Check the type only, as the isinstance test already rules out None.
1 parent 2151e3a commit e8bcbf0

2 files changed

Lines changed: 15 additions & 2 deletions

File tree

‎dspy/primitives/example.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def __init__(self, base=None, **kwargs):
109109
self._input_keys = None
110110

111111
# Initialize from a base Example if provided
112-
if base and isinstance(base, type(self)):
112+
if isinstance(base, type(self)):
113113
self._store = base._store.copy()
114114
# Preserve the input/label split so copy()/without()/Example(base=...)
115115
# don't silently break .inputs()/.labels() on the new instance.
@@ -119,7 +119,7 @@ def __init__(self, base=None, **kwargs):
119119
self._input_keys = set(base_input_keys)
120120

121121
# Initialize from a dict if provided
122-
elif base and isinstance(base, dict):
122+
elif isinstance(base, dict):
123123
self._store = base.copy()
124124

125125
# Update with provided kwargs

‎tests/primitives/test_example.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,19 @@ def test_example_copy_preserves_input_keys():
160160
assert Example(base=example)._input_keys == {"question"}
161161

162162

163+
def test_example_copy_preserves_fields_when_example_has_no_visible_fields():
164+
# An Example whose only contents are `dspy_*` metadata or an input/label split has len() == 0.
165+
# Copying it must still carry over its store and input keys.
166+
template = Example().with_inputs("question")
167+
filled = template.copy(question="q", answer="a")
168+
assert filled._input_keys == {"question"}
169+
assert filled.inputs().toDict() == {"question": "q"}
170+
171+
metadata_only = Example(dspy_uuid="u1", dspy_split="train")
172+
assert metadata_only.copy().toDict() == {"dspy_uuid": "u1", "dspy_split": "train"}
173+
assert Example(base=metadata_only).toDict() == {"dspy_uuid": "u1", "dspy_split": "train"}
174+
175+
163176
def test_prediction_copy_does_not_require_input_keys():
164177
# Example subclasses (e.g. Prediction) don't keep _input_keys; copy() must not crash.
165178
import dspy

0 commit comments

Comments
 (0)