Skip to content

API Reference: Signatures

Signatures define the inputs and outputs for modules. They provide type safety and clear contracts for LLM interactions.

Creating Signatures

Class-based Signatures

The traditional way to define signatures using Python classes:

from udspy import Signature, InputField, OutputField

class QA(Signature):
    """Answer questions concisely."""
    question: str = InputField()
    answer: str = OutputField()

String Signatures (DSPy-style)

For quick prototyping, use the string format "inputs -> outputs":

from udspy import Signature

# Simple signature
QA = Signature.from_string("question -> answer")

# Multiple inputs and outputs
Analyze = Signature.from_string(
    "context, question -> summary, answer",
    "Analyze text and answer questions"
)

Format: "input1, input2 -> output1, output2"

  • Inputs and outputs are comma-separated
  • Whitespace is trimmed automatically
  • All fields default to str type
  • Optional second argument for instructions

Direct Module Usage

All modules automatically recognize string signatures:

from udspy import Predict, ChainOfThought

# These are equivalent
predictor1 = Predict("question -> answer")
predictor2 = Predict(Signature.from_string("question -> answer"))

When to Use Each Format

Use string signatures (from_string) when: - Prototyping quickly - All fields are strings - You don't need field descriptions - The signature is simple

Use class-based signatures when: - You need custom types (int, list, custom Pydantic models) - You want field descriptions for better LLM guidance - The signature is complex - You want IDE autocomplete and type checking

Examples

Basic String Signature

from udspy import Predict

predictor = Predict("question -> answer")
result = predictor(question="What is Python?")
print(result.answer)

Multiple Fields

from udspy import ChainOfThought

cot = ChainOfThought("context, question -> summary, answer")
result = cot(
    context="Python is a programming language",
    question="What is Python?"
)
print(result.reasoning)
print(result.summary)
print(result.answer)

With Instructions

QA = Signature.from_string(
    "question -> answer",
    "Answer questions concisely and accurately"
)
predictor = Predict(QA)

Comparison

# String format - quick and simple
QA_String = Signature.from_string("question -> answer")

# Class format - more control
class QA_Class(Signature):
    """Answer questions."""
    question: str = InputField(description="Question to answer")
    answer: str = OutputField(description="Concise answer")

API Reference

udspy.signature

Signature definitions for structured LLM inputs and outputs.

Classes

Signature

Bases: BaseModel

Base class for defining LLM task signatures.

A Signature specifies the input and output fields for an LLM task, along with an optional instruction describing the task.

Example
class QA(Signature):
    '''Answer questions concisely.'''
    question: str = InputField(description="Question to answer")
    answer: str = OutputField(description="Concise answer")
Source code in src/udspy/signature.py
class Signature(BaseModel, metaclass=SignatureMeta):
    """Base class for defining LLM task signatures.

    A Signature specifies the input and output fields for an LLM task,
    along with an optional instruction describing the task.

    Example:
        ```python
        class QA(Signature):
            '''Answer questions concisely.'''
            question: str = InputField(description="Question to answer")
            answer: str = OutputField(description="Concise answer")
        ```
    """

    @classmethod
    def get_input_fields(cls) -> dict[str, FieldInfo]:
        """Get all input fields defined in this signature."""
        return {
            name: field_info
            for name, field_info in cls.model_fields.items()
            if (field_info.json_schema_extra or {}).get("__udspy_field_type") == "input"  # type: ignore[union-attr]
        }

    @classmethod
    def get_output_fields(cls) -> dict[str, FieldInfo]:
        """Get all output fields defined in this signature."""
        return {
            name: field_info
            for name, field_info in cls.model_fields.items()
            if (field_info.json_schema_extra or {}).get("__udspy_field_type") == "output"  # type: ignore[union-attr]
        }

    @classmethod
    def get_instructions(cls) -> str:
        """Get the task instructions from the docstring."""
        return (cls.__doc__ or "").strip()

    @classmethod
    def from_string(cls, spec: str, instructions: str = "") -> type["Signature"]:
        """Create a Signature from DSPy-style string format.

        This is a convenience method for creating simple signatures using
        the DSPy string format "input1, input2 -> output1, output2".
        All fields default to type `str`.

        For more control over field types, descriptions, and defaults,
        use the class-based Signature definition or `make_signature()`.

        Args:
            spec: Signature specification in format "inputs -> outputs"
                  Examples: "question -> answer"
                           "context, question -> answer"
                           "text -> summary, keywords"
            instructions: Optional task instructions (docstring)

        Returns:
            A new Signature class with all fields as type `str`

        Raises:
            ValueError: If spec is not in valid format

        Example:
            ```python
            # Simple signature
            QA = Signature.from_string("question -> answer", "Answer questions")
            predictor = Predict(QA)

            # Multiple inputs and outputs
            Summarize = Signature.from_string(
                "document, style -> summary, keywords",
                "Summarize documents in specified style"
            )
            ```

        Note:
            This is equivalent to DSPy's string-based signature creation.
            All fields default to `str` type. For custom types, use the
            class-based approach with InputField() and OutputField().
        """
        if "->" not in spec:
            raise ValueError(
                f"Invalid signature format: '{spec}'. "
                "Must be in format 'inputs -> outputs' (e.g., 'question -> answer')"
            )

        parts = spec.split("->")
        if len(parts) != 2:
            raise ValueError(
                f"Invalid signature format: '{spec}'. Must have exactly one '->' separator"
            )

        input_str = parts[0].strip()
        if not input_str:
            raise ValueError("Signature must have at least one input field")

        input_names = [name.strip() for name in input_str.split(",")]
        input_fields: dict[str, type] = {name: str for name in input_names if name}

        output_str = parts[1].strip()
        if not output_str:
            raise ValueError("Signature must have at least one output field")

        output_names = [name.strip() for name in output_str.split(",")]
        output_fields: dict[str, type] = {name: str for name in output_names if name}

        return make_signature(input_fields, output_fields, instructions)
Functions
from_string(spec, instructions='') classmethod

Create a Signature from DSPy-style string format.

This is a convenience method for creating simple signatures using the DSPy string format "input1, input2 -> output1, output2". All fields default to type str.

For more control over field types, descriptions, and defaults, use the class-based Signature definition or make_signature().

Parameters:

Name Type Description Default
spec str

Signature specification in format "inputs -> outputs" Examples: "question -> answer" "context, question -> answer" "text -> summary, keywords"

required
instructions str

Optional task instructions (docstring)

''

Returns:

Type Description
type[Signature]

A new Signature class with all fields as type str

Raises:

Type Description
ValueError

If spec is not in valid format

Example
# Simple signature
QA = Signature.from_string("question -> answer", "Answer questions")
predictor = Predict(QA)

# Multiple inputs and outputs
Summarize = Signature.from_string(
    "document, style -> summary, keywords",
    "Summarize documents in specified style"
)
Note

This is equivalent to DSPy's string-based signature creation. All fields default to str type. For custom types, use the class-based approach with InputField() and OutputField().

Source code in src/udspy/signature.py
@classmethod
def from_string(cls, spec: str, instructions: str = "") -> type["Signature"]:
    """Create a Signature from DSPy-style string format.

    This is a convenience method for creating simple signatures using
    the DSPy string format "input1, input2 -> output1, output2".
    All fields default to type `str`.

    For more control over field types, descriptions, and defaults,
    use the class-based Signature definition or `make_signature()`.

    Args:
        spec: Signature specification in format "inputs -> outputs"
              Examples: "question -> answer"
                       "context, question -> answer"
                       "text -> summary, keywords"
        instructions: Optional task instructions (docstring)

    Returns:
        A new Signature class with all fields as type `str`

    Raises:
        ValueError: If spec is not in valid format

    Example:
        ```python
        # Simple signature
        QA = Signature.from_string("question -> answer", "Answer questions")
        predictor = Predict(QA)

        # Multiple inputs and outputs
        Summarize = Signature.from_string(
            "document, style -> summary, keywords",
            "Summarize documents in specified style"
        )
        ```

    Note:
        This is equivalent to DSPy's string-based signature creation.
        All fields default to `str` type. For custom types, use the
        class-based approach with InputField() and OutputField().
    """
    if "->" not in spec:
        raise ValueError(
            f"Invalid signature format: '{spec}'. "
            "Must be in format 'inputs -> outputs' (e.g., 'question -> answer')"
        )

    parts = spec.split("->")
    if len(parts) != 2:
        raise ValueError(
            f"Invalid signature format: '{spec}'. Must have exactly one '->' separator"
        )

    input_str = parts[0].strip()
    if not input_str:
        raise ValueError("Signature must have at least one input field")

    input_names = [name.strip() for name in input_str.split(",")]
    input_fields: dict[str, type] = {name: str for name in input_names if name}

    output_str = parts[1].strip()
    if not output_str:
        raise ValueError("Signature must have at least one output field")

    output_names = [name.strip() for name in output_str.split(",")]
    output_fields: dict[str, type] = {name: str for name in output_names if name}

    return make_signature(input_fields, output_fields, instructions)
get_input_fields() classmethod

Get all input fields defined in this signature.

Source code in src/udspy/signature.py
@classmethod
def get_input_fields(cls) -> dict[str, FieldInfo]:
    """Get all input fields defined in this signature."""
    return {
        name: field_info
        for name, field_info in cls.model_fields.items()
        if (field_info.json_schema_extra or {}).get("__udspy_field_type") == "input"  # type: ignore[union-attr]
    }
get_instructions() classmethod

Get the task instructions from the docstring.

Source code in src/udspy/signature.py
@classmethod
def get_instructions(cls) -> str:
    """Get the task instructions from the docstring."""
    return (cls.__doc__ or "").strip()
get_output_fields() classmethod

Get all output fields defined in this signature.

Source code in src/udspy/signature.py
@classmethod
def get_output_fields(cls) -> dict[str, FieldInfo]:
    """Get all output fields defined in this signature."""
    return {
        name: field_info
        for name, field_info in cls.model_fields.items()
        if (field_info.json_schema_extra or {}).get("__udspy_field_type") == "output"  # type: ignore[union-attr]
    }

SignatureMeta

Bases: type(BaseModel)

Metaclass for Signature that validates field types.

Source code in src/udspy/signature.py
class SignatureMeta(type(BaseModel)):  # type: ignore[misc]
    """Metaclass for Signature that validates field types."""

    def __new__(
        mcs,
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, Any],
        **kwargs: Any,
    ) -> type:
        cls = super().__new__(mcs, name, bases, namespace, **kwargs)

        # Skip validation for the base Signature class
        if name == "Signature":
            return cls

        for field_name, field_info in cls.model_fields.items():
            if not isinstance(field_info, FieldInfo):
                continue

            json_schema_extra = field_info.json_schema_extra or {}
            field_type = json_schema_extra.get("__udspy_field_type")  # type: ignore[union-attr]

            if field_type not in ("input", "output"):
                raise TypeError(
                    f"Field '{field_name}' in {name} must be declared with "
                    f"InputField() or OutputField()"
                )

        return cls

Functions

InputField(default=..., *, description=None, **kwargs)

Define an input field for a Signature.

Parameters:

Name Type Description Default
default Any

Default value for the field

...
description str | None

Human-readable description of the field's purpose

None
**kwargs Any

Additional Pydantic field arguments

{}

Returns:

Type Description
Any

A Pydantic FieldInfo with input metadata

Source code in src/udspy/signature.py
def InputField(
    default: Any = ...,
    *,
    description: str | None = None,
    **kwargs: Any,
) -> Any:
    """Define an input field for a Signature.

    Args:
        default: Default value for the field
        description: Human-readable description of the field's purpose
        **kwargs: Additional Pydantic field arguments

    Returns:
        A Pydantic FieldInfo with input metadata
    """
    json_schema_extra = kwargs.pop("json_schema_extra", {})
    json_schema_extra["__udspy_field_type"] = "input"

    return Field(
        default=default,
        description=description,
        json_schema_extra=json_schema_extra,
        **kwargs,
    )

OutputField(default=..., *, description=None, **kwargs)

Define an output field for a Signature.

Parameters:

Name Type Description Default
default Any

Default value for the field

...
description str | None

Human-readable description of the field's purpose

None
**kwargs Any

Additional Pydantic field arguments

{}

Returns:

Type Description
Any

A Pydantic FieldInfo with output metadata

Source code in src/udspy/signature.py
def OutputField(
    default: Any = ...,
    *,
    description: str | None = None,
    **kwargs: Any,
) -> Any:
    """Define an output field for a Signature.

    Args:
        default: Default value for the field
        description: Human-readable description of the field's purpose
        **kwargs: Additional Pydantic field arguments

    Returns:
        A Pydantic FieldInfo with output metadata
    """
    json_schema_extra = kwargs.pop("json_schema_extra", {})
    json_schema_extra["__udspy_field_type"] = "output"

    return Field(
        default=default,
        description=description,
        json_schema_extra=json_schema_extra,
        **kwargs,
    )

make_signature(input_fields, output_fields, instructions='')

Dynamically create a Signature class.

Parameters:

Name Type Description Default
input_fields dict[str, type]

Dictionary mapping field names to types for inputs

required
output_fields dict[str, type]

Dictionary mapping field names to types for outputs

required
instructions str

Task instructions

''

Returns:

Type Description
type[Signature]

A new Signature class

Example
QA = make_signature(
    {"question": str},
    {"answer": str},
    "Answer questions concisely"
)
Source code in src/udspy/signature.py
def make_signature(
    input_fields: dict[str, type],
    output_fields: dict[str, type],
    instructions: str = "",
) -> type[Signature]:
    """Dynamically create a Signature class.

    Args:
        input_fields: Dictionary mapping field names to types for inputs
        output_fields: Dictionary mapping field names to types for outputs
        instructions: Task instructions

    Returns:
        A new Signature class

    Example:
        ```python
        QA = make_signature(
            {"question": str},
            {"answer": str},
            "Answer questions concisely"
        )
        ```
    """
    fields = {}

    for name, type_ in input_fields.items():
        fields[name] = (type_, InputField())

    for name, type_ in output_fields.items():
        fields[name] = (type_, OutputField())

    sig = create_model(
        "DynamicSignature",
        __base__=Signature,
        **fields,  # type: ignore
    )

    if instructions:
        sig.__doc__ = instructions

    return sig