
Dspy Signatures
- 6 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Helps with ai & agent building tasks.
About
dspy-signatures is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- dspy-signatures
- AI & Agent Building
- AI-coding skill
Dspy Signatures by the numbers
- 6 all-time installs (skills.sh)
- Ranked #12,779 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lebsral/dspy-programming-not-prompting-lms-skills --skill dspy-signaturesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 11 |
| Last updated | June 28, 2026 |
| Repository | lebsral/dspy-programming-not-prompting-lms-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
DSPy Signatures
Guide the user through defining DSPy Signatures — typed declarations of what goes into and comes out of an LM call.
Step 1: What kind of signature?
Ask the user before diving in:
1. How complex is your I/O? One input, one output (use inline)? Multiple fields, type constraints, or nested objects (use class-based)? 2. Do you need structured output? If the output maps to a database model or API response, you likely want a Pydantic model as the output type. 3. Are outputs constrained? If you need categories, booleans, or numeric ranges, you need type annotations.
Then jump to the relevant section below.
What is a Signature
A Signature declares the input/output contract for an LM call -- field names, types, and descriptions. DSPy compiles it into an optimized prompt automatically. You define the I/O spec; DSPy handles the prompting.
When to use each style
| Style | When to use | Example |
|---|---|---|
| Inline | Quick one-liner, 1-2 inputs, 1 output, string types | "question -> answer" |
| Class-based | Multiple fields, type constraints, descriptions, Pydantic outputs | class Classify(dspy.Signature) |
Rule of thumb: Start inline for prototyping. Switch to class-based when you need type constraints, field descriptions, or more than one output.
Inline signatures
Inline signatures are strings with -> separating inputs from outputs: "question -> answer", "text -> label: bool". Supported type suffixes: str (default), int, float, bool, list[str].
Class-based signatures
Class-based signatures give you type constraints, field descriptions, and a docstring that acts as the task instruction. Use them when you need more than a one-liner.
Field options
Both InputField and OutputField accept these parameters:
class Example(dspy.Signature):
"""Demonstrate field options."""
# desc: describes the field to the LM (shows up in the prompt)
text: str = dspy.InputField(desc="The document to analyze")
# type constraint via inline annotation (preferred)
category: Literal["news", "blog", "research"] = dspy.OutputField(desc="The document category")- `desc` — a natural language description. Helps the LM understand what the field means. Use this when the field name alone is ambiguous.
- `type_` — sets the type constraint on the field. Still supported, but prefer an inline Python annotation (category: Literal[...] = dspy.OutputField(...)).
Pydantic models as output types
For complex or nested structured output, use a Pydantic BaseModel as the output type. DSPy handles serialization and validation automatically.
import dspy
from pydantic import BaseModel, Field
from typing import Optional
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class LineItem(BaseModel):
description: str
quantity: int
unit_price: float
class Invoice(BaseModel):
vendor: str
date: str = Field(description="Invoice date in YYYY-MM-DD format")
total: float
items: list[LineItem]
notes: Optional[str] = None
class ParseInvoice(dspy.Signature):
"""Extract structured invoice data from the raw text."""
text: str = dspy.InputField(desc="Raw invoice text")
invoice: Invoice = dspy.OutputField(desc="Parsed invoice data")
parser = dspy.ChainOfThought(ParseInvoice)
result = parser(text="Invoice from Acme Corp, Jan 15 2025. 2x Widget ($10 each), 1x Gadget ($25). Total: $45.")
print(result.invoice.vendor) # Acme Corp
print(result.invoice.items[0]) # LineItem(description='Widget', quantity=2, unit_price=10.0)
print(result.invoice.total) # 45.0When to use Pydantic outputs:
- You need nested objects (addresses, line items, etc.)
- You want automatic validation (Pydantic enforces types)
- The output maps to a database model or API response
- You need
Optionalfields for data that may not be present
Common patterns
Docstrings as task instructions
The docstring is the most important part of a class-based signature. DSPy uses it as the primary instruction to the LM.
# Vague — the LM doesn't know what "classify" means in your context
class Bad(dspy.Signature):
"""Classify the text."""
text: str = dspy.InputField()
label: str = dspy.OutputField()
# Clear — tells the LM exactly what to do
class Good(dspy.Signature):
"""Classify the customer support message into a department for routing.
Consider the primary intent, not just keywords."""
message: str = dspy.InputField(desc="Customer support message")
department: Literal["billing", "technical", "account", "general"] = dspy.OutputField()Advanced: dynamic signatures
For runtime customization without defining new classes:
# Override instructions at call time (inline signatures)
predict = dspy.Predict("question -> answer", instructions="Answer in exactly one sentence.")
# Programmatically modify a class-based signature
MySignature = MySignature.with_instructions("New instructions for this run")
# Add/remove fields dynamically
MySignature = MySignature.append("confidence", dspy.OutputField(), type_=float)
MySignature = MySignature.delete("unused_field")DSPy also supports special input types: dspy.Image for image inputs and dspy.History for conversation history.
When NOT to use class-based signatures
- Simple extraction or Q&A — if
"question -> answer"captures your task, an inline signature is clearer and shorter. Do not over-engineer with a class when a string works. - Prototyping — start inline, switch to class-based only when you need type constraints, descriptions, or Pydantic outputs.
- Too many output fields — if you need more than 4-5 outputs, the LM quality degrades. Split into multiple calls with simpler signatures instead.
Gotchas
1. Field names ARE the prompt -- text -> summary works better than input -> output because DSPy uses field names directly in the generated prompt. Choose descriptive names. 2. Literal types need `tuple()` wrapping for dynamic values -- use Literal[tuple(["a", "b"])] not Literal[["a", "b"]] when constructing from a list at runtime. 3. Keep signatures small -- more than 4-5 output fields degrades quality. Split into multiple calls instead. 4. The docstring on a Signature class becomes the task instruction -- write it carefully, as a clear directive. A vague docstring like "Classify the text" performs much worse than "Classify the customer support message into a department for routing." 5. Field `desc` values are NOT optimized -- DSPy optimizers (GEPA, MIPROv2, COPRO) tune the Signature docstring and/or few-shot demos, but InputField(desc=...), OutputField(desc=...), and Pydantic Field(description=...) values are fixed. If your structured output task relies heavily on field descriptions for guidance, see /dspy-gepa for a workaround that flattens field descriptions into the instruction for optimization.
Additional resources
- dspy.Signature API docs
- dspy.InputField API docs
- dspy.OutputField API docs
- For API details, see reference.md
- For worked examples, see examples.md
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- Using signatures with modules — see
/dspy-predict(Predict),/dspy-chain-of-thought(ChainOfThought),/dspy-modules(custom modules) - Parsing structured data from text — see
/ai-parsing-data - Classification and sorting — see
/ai-sorting - Install `/ai-do` if you do not have it — it routes any AI problem to the right skill and is the fastest way to work:
npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill ai-do
[
{
"id": "dspy-signatures-inline-vs-class",
"prompt": "I need to classify customer messages into categories. Should I use an inline signature or a class-based one?",
"should_contain": ["Literal", "dspy.Signature", "OutputField"],
"should_not_contain": [],
"notes": "Should recommend class-based for classification with Literal type constraint. Should show the class-based pattern with docstring and OutputField."
},
{
"id": "dspy-signatures-pydantic-output",
"prompt": "I want to extract structured invoice data from raw text — vendor, date, line items with quantities and prices. How do I define the DSPy signature?",
"should_contain": ["BaseModel", "dspy.Signature", "OutputField", "list"],
"should_not_contain": [],
"notes": "Should use Pydantic BaseModel for nested output (LineItem inside Invoice). Should show InputField for raw text and OutputField with the Pydantic type."
},
{
"id": "dspy-signatures-field-descriptions",
"prompt": "My DSPy signature works but the LM keeps misunderstanding what the output fields mean. How do I improve it?",
"should_contain": ["desc", "docstring"],
"should_not_contain": [],
"notes": "Should recommend adding desc= to OutputField and improving the Signature docstring. Should mention that field names themselves matter since DSPy uses them in the prompt."
}
]
Signature Examples
Email Classifier
Classify incoming emails into categories using a class-based signature with Literal output.
import dspy
from typing import Literal
# Configure any LM provider
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
CATEGORIES = ["sales_inquiry", "support_request", "billing", "spam", "partnership", "other"]
class ClassifyEmail(dspy.Signature):
"""Classify an incoming email by intent for routing to the correct team.
Focus on the sender's primary intent, not just keywords."""
subject: str = dspy.InputField(desc="Email subject line")
body: str = dspy.InputField(desc="Email body text")
category: Literal[tuple(CATEGORIES)] = dspy.OutputField(desc="Primary intent category")
priority: Literal["low", "medium", "high"] = dspy.OutputField(
desc="Urgency: high = revenue impact or angry customer, medium = needs response today, low = can wait"
)
classifier = dspy.ChainOfThought(ClassifyEmail)
# Test it
result = classifier(
subject="Urgent: billing discrepancy on last invoice",
body="Hi, I was charged $500 instead of the agreed $350 on invoice #1042. "
"This is the third billing error this quarter. Please fix this ASAP or "
"we will need to reconsider our contract."
)
print(f"Category: {result.category}") # billing
print(f"Priority: {result.priority}") # high
print(f"Reasoning: {result.reasoning}") # ChainOfThought explains its logic
# Batch classify
emails = [
{"subject": "Partnership opportunity", "body": "We'd love to integrate your API into our platform."},
{"subject": "Can't log in", "body": "Password reset isn't working, tried 5 times."},
{"subject": "RE: RE: RE: pricing", "body": "What's the cost for 500 seats?"},
]
for email in emails:
r = classifier(subject=email["subject"], body=email["body"])
print(f" {email['subject'][:40]:40s} -> {r.category:20s} ({r.priority})")Expected output:
Category: billing
Priority: high
Reasoning: The customer reports a billing error ...
Partnership opportunity -> partnership (medium)
Can't log in -> support_request (medium)
RE: RE: RE: pricing -> sales_inquiry (low)Invoice Parser
Extract structured invoice data using a Pydantic model as the output type.
import dspy
from pydantic import BaseModel, Field
from typing import Optional
# Configure any LM provider
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class LineItem(BaseModel):
description: str = Field(description="Item or service description")
quantity: int = Field(description="Number of units", ge=1)
unit_price: float = Field(description="Price per unit in dollars", ge=0)
total: float = Field(description="Line total (quantity * unit_price)", ge=0)
class InvoiceData(BaseModel):
vendor_name: str
invoice_number: Optional[str] = None
invoice_date: str = Field(description="Date in YYYY-MM-DD format")
due_date: Optional[str] = Field(default=None, description="Due date in YYYY-MM-DD format")
items: list[LineItem]
subtotal: float
tax: Optional[float] = None
total: float
currency: str = Field(default="USD", description="ISO 4217 currency code")
class ParseInvoice(dspy.Signature):
"""Extract structured invoice data from raw text. Return None for optional
fields that are not present in the text -- do not guess."""
raw_text: str = dspy.InputField(desc="Raw invoice text, possibly messy or OCR output")
invoice: InvoiceData = dspy.OutputField(desc="Structured invoice data")
parser = dspy.ChainOfThought(ParseInvoice)
# Test with a sample invoice
invoice_text = """
INVOICE #2025-0042
From: Stellar Design Co.
Date: March 3, 2025
Due: April 2, 2025
Description Qty Unit Price Total
---------------------------------------------------------
Website redesign 1 $3,500.00 $3,500.00
Logo design (revisions x3) 1 $1,200.00 $1,200.00
Stock photography 10 $25.00 $250.00
Hosting setup 1 $150.00 $150.00
Subtotal: $5,100.00
Tax (8%): $408.00
TOTAL: $5,508.00
Payment terms: Net 30
"""
result = parser(raw_text=invoice_text)
inv = result.invoice
print(f"Vendor: {inv.vendor_name}")
print(f"Invoice #: {inv.invoice_number}")
print(f"Date: {inv.invoice_date}")
print(f"Due: {inv.due_date}")
print(f"\nLine items:")
for item in inv.items:
print(f" {item.description:30s} {item.quantity:3d} x ${item.unit_price:>8.2f} = ${item.total:>9.2f}")
print(f"\nSubtotal: ${inv.subtotal:,.2f}")
print(f"Tax: ${inv.tax:,.2f}")
print(f"Total: ${inv.total:,.2f}")
# Convert to dict for JSON/API use
print(f"\nAs dict: {inv.model_dump()}")Expected output:
Vendor: Stellar Design Co.
Invoice #: 2025-0042
Date: 2025-03-03
Due: 2025-04-02
Line items:
Website redesign 1 x $ 3500.00 = $ 3500.00
Logo design (revisions x3) 1 x $ 1200.00 = $ 1200.00
Stock photography 10 x $ 25.00 = $ 250.00
Hosting setup 1 x $ 150.00 = $ 150.00
Subtotal: $5,100.00
Tax: $408.00
Total: $5,508.00Multi-Output Content Analysis
Analyze a piece of content across multiple dimensions with different output types.
import dspy
from typing import Literal
from pydantic import BaseModel
# Configure any LM provider
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
class AudienceProfile(BaseModel):
primary_audience: str
expertise_level: str
industry: str
class AnalyzeContent(dspy.Signature):
"""Perform a comprehensive analysis of a blog post or article.
Evaluate readability for a general tech audience."""
title: str = dspy.InputField(desc="Article title")
content: str = dspy.InputField(desc="Full article text")
# String output
summary: str = dspy.OutputField(desc="2-3 sentence summary of the article")
# Literal output
content_type: Literal["tutorial", "opinion", "news", "case_study", "review"] = dspy.OutputField()
# List output
topics: list[str] = dspy.OutputField(desc="Key topics covered, max 5")
# Integer output
estimated_read_minutes: int = dspy.OutputField(desc="Estimated reading time in minutes")
# Float output
readability_score: float = dspy.OutputField(desc="Readability from 0.0 (very technical) to 1.0 (very accessible)")
# Boolean output
has_code_examples: bool = dspy.OutputField(desc="Whether the article contains code snippets")
# Pydantic model output
audience: AudienceProfile = dspy.OutputField(desc="Target audience profile")
analyzer = dspy.ChainOfThought(AnalyzeContent)
# Test with a sample article
result = analyzer(
title="Building Real-Time Data Pipelines with Apache Kafka and Python",
content="""
In this tutorial, we'll walk through setting up a real-time data pipeline
using Apache Kafka as the message broker and Python consumers. We'll cover
topic configuration, producer setup, consumer groups, and error handling.
First, install the confluent-kafka package:
pip install confluent-kafka
Here's a basic producer:
from confluent_kafka import Producer
producer = Producer({'bootstrap.servers': 'localhost:9092'})
producer.produce('my-topic', value='hello world')
producer.flush()
For production use, you'll want to handle serialization with Avro or Protobuf,
implement proper error callbacks, and monitor consumer lag. We've used this
pattern at scale processing 50,000 events per second with a team of 3 engineers.
"""
)
print(f"Summary: {result.summary}")
print(f"Type: {result.content_type}")
print(f"Topics: {result.topics}")
print(f"Read time: {result.estimated_read_minutes} min")
print(f"Readability: {result.readability_score}")
print(f"Has code: {result.has_code_examples}")
print(f"Audience: {result.audience.primary_audience} ({result.audience.expertise_level})")
print(f"Industry: {result.audience.industry}")Expected output:
Summary: A hands-on tutorial for building real-time data pipelines using Apache Kafka with Python. Covers producer setup, consumer groups, and production considerations for high-throughput event processing.
Type: tutorial
Topics: ['Apache Kafka', 'Python', 'data pipelines', 'real-time processing', 'event streaming']
Read time: 4
Readability: 0.6
Has code: True
Audience: Backend developers (intermediate)
Industry: software engineeringSignatures API Reference
Condensed from dspy.ai/api/signatures. Verify against upstream for latest.
dspy.Signature
Base class for defining LM call contracts. Subclass it to create typed signatures.
class MySignature(dspy.Signature):
"""Task instruction goes here."""
input_field: str = dspy.InputField(desc="description")
output_field: str = dspy.OutputField(desc="description")Class Methods
| Method | Signature | Description |
|---|---|---|
with_instructions | (instructions: str) -> type[Signature] | Return new Signature with replaced instructions |
append | (name: str, field, type_=None) -> type[Signature] | Add field at end of inputs/outputs |
prepend | (name: str, field, type_=None) -> type[Signature] | Insert field at position 0 |
insert | (index: int, name: str, field, type_=None) -> type[Signature] | Insert field at specific index |
delete | (name: str) -> type[Signature] | Remove a field (no error if absent) |
with_updated_fields | (name: str, type_=None, **kwargs) -> type[Signature] | Update field metadata |
equals | (other) -> bool | Compare JSON schemas |
All methods are non-mutating -- they return new Signature classes.
dspy.InputField
dspy.InputField(desc=None, prefix=None, **kwargs)| Parameter | Type | Default | Description |
|---|---|---|---|
desc | `str \ | None` | None |
prefix | `str \ | None` | None |
**kwargs | Passed to pydantic.Field() |
dspy.OutputField
dspy.OutputField(desc=None, prefix=None, type_=None, **kwargs)| Parameter | Type | Default | Description |
|---|---|---|---|
desc | `str \ | None` | None |
prefix | `str \ | None` | None |
type_ | `type \ | None` | None |
**kwargs | Passed to pydantic.Field() |
Inline Signatures
String shorthand: "input1, input2 -> output1, output2". Supports type suffixes: str (default), int, float, bool, list[str].
"question -> answer"
"text -> label: bool"
"context, question -> answer, confidence: float"