diff --git a/dspy/signatures/signature.py b/dspy/signatures/signature.py index 236a774ddd..b490aed64c 100644 --- a/dspy/signatures/signature.py +++ b/dspy/signatures/signature.py @@ -245,6 +245,31 @@ class Signature(BaseModel, metaclass=SignatureMeta): @classmethod def with_instructions(cls, instructions: str) -> type["Signature"]: + """Return a new Signature class with identical fields and new instructions. + + This method does not mutate `cls`. It constructs a fresh Signature + class using the current fields and the provided `instructions`. + + Args: + instructions (str): Instruction text to attach to the new signature. + + Returns: + A new Signature class whose fields match `cls.fields` + and whose instructions equal `instructions`. + + Example: + ```python + import dspy + + class MySig(dspy.Signature): + input_text: str = dspy.InputField(desc="Input text") + output_text: str = dspy.OutputField(desc="Output text") + + NewSig = MySig.with_instructions("Translate to French.") + assert NewSig is not MySig + assert NewSig.instructions == "Translate to French." + ``` + """ return Signature(cls.fields, instructions) @classmethod @@ -275,14 +300,87 @@ def with_updated_fields(cls, name: str, type_: type | None = None, **kwargs: dic @classmethod def prepend(cls, name, field, type_=None) -> type["Signature"]: + """Insert a field at index 0 of the `inputs` or `outputs` section. + + Args: + name (str): Field name to add. + field: `InputField` or `OutputField` instance to insert. + type_ (type | None): Optional explicit type annotation. If `type_` is `None`, the effective type is + resolved by `insert`. + + Returns: + A new `Signature` class with the field inserted first. + + Example: + ```python + import dspy + + class MySig(dspy.Signature): + input_text: str = dspy.InputField(desc="Input sentence") + output_text: str = dspy.OutputField(desc="Translated sentence") + + NewSig = MySig.prepend("context", dspy.InputField(desc="Context for translation")) + print(list(NewSig.fields.keys())) + ``` + """ return cls.insert(0, name, field, type_) @classmethod def append(cls, name, field, type_=None) -> type["Signature"]: + """Insert a field at the end of the `inputs` or `outputs` section. + + Args: + name (str): Field name to add. + field: `InputField` or `OutputField` instance to insert. + type_ (type | None): Optional explicit type annotation. If `type_` is `None`, the effective type is + resolved by `insert`. + + Returns: + A new Signature class with the field appended. + + Example: + ```python + import dspy + + class MySig(dspy.Signature): + input_text: str = dspy.InputField(desc="Input sentence") + output_text: str = dspy.OutputField(desc="Translated sentence") + + NewSig = MySig.append("confidence", dspy.OutputField(desc="Translation confidence")) + print(list(NewSig.fields.keys())) + ``` + """ return cls.insert(-1, name, field, type_) @classmethod def delete(cls, name) -> type["Signature"]: + """Return a new Signature class without the given field. + + If `name` is not present, the fields are unchanged (no error raised). + + Args: + name (str): Field name to remove. + + Returns: + A new Signature class with the field removed (or unchanged if the field was absent). + + Example: + ```python + import dspy + + class MySig(dspy.Signature): + input_text: str = dspy.InputField(desc="Input sentence") + temp_field: str = dspy.InputField(desc="Temporary debug field") + output_text: str = dspy.OutputField(desc="Translated sentence") + + NewSig = MySig.delete("temp_field") + print(list(NewSig.fields.keys())) + + # No error is raised if the field is not present + Unchanged = NewSig.delete("nonexistent") + print(list(Unchanged.fields.keys())) + ``` + """ fields = dict(cls.fields) fields.pop(name, None) @@ -291,6 +389,38 @@ def delete(cls, name) -> type["Signature"]: @classmethod def insert(cls, index: int, name: str, field, type_: type | None = None) -> type["Signature"]: + """Insert a field at a specific position among inputs or outputs. + + Negative indices are supported (e.g., `-1` appends). If `type_` is omitted, the field's + existing `annotation` is used; if that is missing, `str` is used. + + Args: + index (int): Insertion position within the chosen section; negatives append. + name (str): Field name to add. + field: InputField or OutputField instance to insert. + type_ (type | None): Optional explicit type annotation. + + Returns: + A new Signature class with the field inserted. + + Raises: + ValueError: If `index` falls outside the valid range for the chosen section. + + Example: + ```python + import dspy + + class MySig(dspy.Signature): + input_text: str = dspy.InputField(desc="Input sentence") + output_text: str = dspy.OutputField(desc="Translated sentence") + + NewSig = MySig.insert(0, "context", dspy.InputField(desc="Context for translation")) + print(list(NewSig.fields.keys())) + + NewSig2 = NewSig.insert(-1, "confidence", dspy.OutputField(desc="Translation confidence")) + print(list(NewSig2.fields.keys())) + ``` + """ # It's possible to set the type as annotation=type in pydantic.Field(...) # But this may be annoying for users, so we allow them to pass the type if type_ is None: @@ -430,7 +560,9 @@ class MyType: # program of thought and teleprompters, so we just silently default to string. if type_ is None: type_ = str - if not isinstance(type_, (type, typing._GenericAlias, types.GenericAlias, typing._SpecialForm, types.UnionType)): + if not isinstance( + type_, (type, typing._GenericAlias, types.GenericAlias, typing._SpecialForm, types.UnionType) + ): raise ValueError(f"Field types must be types, but received: {type_} of type {type(type_)}.") if not isinstance(field, FieldInfo): raise ValueError(f"Field values must be Field instances, but received: {field}.")