Signatures define the input/output contract for LLM tasks using Pydantic models.
There are three ways to create signatures in udspy:
For rapid prototyping, use the DSPy-style string format:
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"
- All fields default to
strtype - Optional second argument for instructions
- Great for quick prototyping
For production code, use class-based signatures:
from udspy import Signature, InputField, OutputField
class QA(Signature):
"""Answer questions concisely and accurately."""
question: str = InputField(description="Question to answer")
answer: str = OutputField(description="Concise answer")Benefits:
- Custom field types
- Field descriptions for better prompts
- IDE autocomplete and type checking
- Better for complex signatures
For runtime signature creation:
from udspy import make_signature
QA = make_signature(
input_fields={"question": str},
output_fields={"answer": str},
instructions="Answer questions concisely",
)The class docstring becomes the task instruction in the system prompt:
class Summarize(Signature):
"""Summarize the given text in 2-3 sentences."""
text: str = InputField()
summary: str = OutputField()Marks a field as an input:
question: str = InputField(
description="Question to answer", # Used in prompt
default="", # Optional default value
)Marks a field as an output:
answer: str = OutputField(
description="Concise answer",
)Signatures support various field types:
class Example(Signature):
"""Example signature."""
text: str = InputField()
count: int = InputField()
score: float = InputField()
enabled: bool = InputField()class Example(Signature):
"""Example signature."""
tags: list[str] = InputField()
metadata: dict[str, Any] = InputField()from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
class Example(Signature):
"""Example signature."""
person: Person = InputField()
related: list[Person] = OutputField()Signatures use Pydantic for validation:
class Sentiment(Signature):
"""Analyze sentiment."""
text: str = InputField()
sentiment: Literal["positive", "negative", "neutral"] = OutputField()
# Output will be validated to match literal valuesSignatures can have multiple outputs:
class ReasonedQA(Signature):
"""Answer with step-by-step reasoning."""
question: str = InputField()
reasoning: str = OutputField(description="Reasoning process")
answer: str = OutputField(description="Final answer")# Good
question: str = InputField(description="User's question about the product")
# Bad
question: str = InputField()# Good
class Summarize(Signature):
"""Summarize in exactly 3 bullet points, each under 20 words."""
# Bad
class Summarize(Signature):
"""Summarize."""# Good - use Pydantic models for complex outputs
class Analysis(BaseModel):
sentiment: str
confidence: float
keywords: list[str]
class Analyze(Signature):
"""Analyze text."""
text: str = InputField()
analysis: Analysis = OutputField()
# Bad - use many separate fields
class Analyze(Signature):
"""Analyze text."""
text: str = InputField()
sentiment: str = OutputField()
confidence: float = OutputField()
keywords: list[str] = OutputField()Use when:
- Prototyping quickly
- All fields are strings
- Signature is simple
- You don't need field descriptions
# Perfect for quick tests
predictor = Predict("question -> answer")Use when:
- Building production code
- You need custom types
- You want field descriptions
- Signature is complex
class QA(Signature):
"""Answer questions."""
question: str = InputField(description="User's question")
answer: str = OutputField(description="Concise answer")Use when:
- Creating signatures at runtime
- Building signature builders/factories
- Signature structure depends on config
# Build signature based on config
fields = load_field_config()
MySignature = make_signature(fields["inputs"], fields["outputs"])All modules automatically recognize string signatures:
from udspy import Predict, ChainOfThought, ReAct
# All of these work:
predictor1 = Predict("question -> answer")
predictor2 = Predict(QA) # Class-based
predictor3 = Predict(make_signature(...)) # Dynamic
cot = ChainOfThought("question -> answer")
agent = ReAct("question -> answer", tools=[...])See API: Signatures for detailed API documentation including the full Signature.from_string() reference.