home / skills / omidzamani / dspy-skills / dspy-signature-designer

dspy-signature-designer skill

/skills/dspy-signature-designer

This skill designs type-safe DSPy module signatures with input/output schemas and Pydantic models for robust, structured results.

npx playbooks add skill omidzamani/dspy-skills --skill dspy-signature-designer

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.5 KB
---
name: dspy-signature-designer
version: "1.0.0"
dspy-compatibility: "3.1.2"
description: This skill should be used when the user asks to "create a DSPy signature", "define inputs and outputs", "design a signature", "use InputField or OutputField", "add type hints to DSPy", mentions "signature class", "type-safe DSPy", "Pydantic models in DSPy", or needs to define what a DSPy module should do with structured inputs and outputs.
allowed-tools:
  - Read
  - Write
  - Glob
  - Grep
---

# DSPy Signature Designer

## Goal

Design clear, type-safe signatures that define what your DSPy modules should do.

## When to Use

- Defining new DSPy modules
- Need structured/validated outputs
- Complex input/output relationships
- Multi-field responses

## Inputs

| Input | Type | Description |
|-------|------|-------------|
| `task_description` | `str` | What the module should do |
| `input_fields` | `list` | Required inputs |
| `output_fields` | `list` | Expected outputs |
| `type_constraints` | `dict` | Type hints for fields |

## Outputs

| Output | Type | Description |
|--------|------|-------------|
| `signature` | `dspy.Signature` | Type-safe signature class |

## Workflow

### Inline Signatures (Simple)

```python
import dspy

# Basic
qa = dspy.Predict("question -> answer")

# With types
classify = dspy.Predict("sentence -> sentiment: bool")

# Multiple fields
rag = dspy.ChainOfThought("context: list[str], question: str -> answer: str")
```

### Class-based Signatures (Complex)

```python
from typing import Literal, Optional
import dspy

class EmotionClassifier(dspy.Signature):
    """Classify the emotion expressed in the text."""
    
    text: str = dspy.InputField(desc="The text to analyze")
    emotion: Literal['joy', 'sadness', 'anger', 'fear', 'surprise'] = dspy.OutputField()
    confidence: float = dspy.OutputField(desc="Confidence score 0-1")
```

## Type Hints Reference

```python
from typing import Literal, Optional, List
from pydantic import BaseModel

# Basic types
field: str = dspy.InputField()
field: int = dspy.OutputField()
field: float = dspy.OutputField()
field: bool = dspy.OutputField()

# Collections
field: list[str] = dspy.InputField()
field: List[int] = dspy.OutputField()

# Optional
field: Optional[str] = dspy.OutputField()

# Constrained
field: Literal['a', 'b', 'c'] = dspy.OutputField()

# Pydantic models
class Person(BaseModel):
    name: str
    age: int

field: Person = dspy.OutputField()
```

## Production Examples

### Summarization

```python
class Summarize(dspy.Signature):
    """Summarize the document into key points."""
    
    document: str = dspy.InputField(desc="Full document text")
    max_points: int = dspy.InputField(desc="Maximum bullet points", default=5)
    
    summary: list[str] = dspy.OutputField(desc="Key points as bullet list")
    word_count: int = dspy.OutputField(desc="Total words in summary")
```

### Entity Extraction

```python
from pydantic import BaseModel
from typing import List

class Entity(BaseModel):
    text: str
    type: str
    start: int
    end: int

class ExtractEntities(dspy.Signature):
    """Extract named entities from text."""
    
    text: str = dspy.InputField()
    entity_types: list[str] = dspy.InputField(
        desc="Types to extract: PERSON, ORG, LOC, DATE",
        default=["PERSON", "ORG", "LOC"]
    )
    
    entities: List[Entity] = dspy.OutputField()
```

### Multi-Label Classification

```python
class MultiLabelClassify(dspy.Signature):
    """Classify text into multiple categories."""
    
    text: str = dspy.InputField()
    
    categories: list[str] = dspy.OutputField(
        desc="Applicable categories from: tech, business, sports, entertainment"
    )
    primary_category: str = dspy.OutputField(desc="Most relevant category")
    reasoning: str = dspy.OutputField(desc="Explanation for classification")
```

### RAG with Confidence

```python
class GroundedAnswer(dspy.Signature):
    """Answer questions using retrieved context with confidence."""
    
    context: list[str] = dspy.InputField(desc="Retrieved passages")
    question: str = dspy.InputField()
    
    answer: str = dspy.OutputField(desc="Factual answer from context")
    confidence: Literal['high', 'medium', 'low'] = dspy.OutputField(
        desc="Confidence based on context support"
    )
    source_passage: int = dspy.OutputField(
        desc="Index of most relevant passage (0-based)"
    )
```

### Complete Module with Signature

```python
import dspy
from typing import Literal, Optional
import logging

logger = logging.getLogger(__name__)

class AnalyzeSentiment(dspy.Signature):
    """Analyze sentiment with detailed breakdown."""
    
    text: str = dspy.InputField(desc="Text to analyze")
    
    sentiment: Literal['positive', 'negative', 'neutral', 'mixed'] = dspy.OutputField()
    score: float = dspy.OutputField(desc="Sentiment score from -1 to 1")
    aspects: list[str] = dspy.OutputField(desc="Key aspects mentioned")
    reasoning: str = dspy.OutputField(desc="Explanation of sentiment")

class SentimentAnalyzer(dspy.Module):
    def __init__(self):
        self.analyze = dspy.ChainOfThought(AnalyzeSentiment)
    
    def forward(self, text: str):
        try:
            result = self.analyze(text=text)
            
            # Validate score range
            if hasattr(result, 'score'):
                result.score = max(-1, min(1, float(result.score)))
            
            return result
            
        except Exception as e:
            logger.error(f"Analysis failed: {e}")
            return dspy.Prediction(
                sentiment='neutral',
                score=0.0,
                aspects=[],
                reasoning="Analysis failed"
            )

# Usage
analyzer = SentimentAnalyzer()
result = analyzer(text="The product quality is great but shipping was slow.")
print(f"Sentiment: {result.sentiment} ({result.score})")
print(f"Aspects: {result.aspects}")
```

## Best Practices

1. **Descriptive docstrings** - The class docstring becomes the task instruction
2. **Field descriptions** - Guide the model with `desc` parameter
3. **Constrain outputs** - Use `Literal` for categorical outputs
4. **Default values** - Provide sensible defaults for optional inputs
5. **Validate types** - Pydantic models ensure structured output

## Advanced Field Options

```python
# Constraints (available in 3.1.2+)
class ConstrainedSignature(dspy.Signature):
    """Example with validation constraints."""

    text: str = dspy.InputField(
        min_length=5,
        max_length=100,
        desc="Input text between 5-100 chars"
    )
    number: int = dspy.InputField(
        gt=0,
        lt=10,
        desc="Number between 0 and 10"
    )
    score: float = dspy.OutputField(
        ge=0.0,
        le=1.0,
        desc="Score between 0 and 1"
    )
    count: int = dspy.OutputField(
        multiple_of=2,
        desc="Even number count"
    )

# Prefix and format
class FormattedSignature(dspy.Signature):
    """Example with custom prefix and format."""

    goal: str = dspy.InputField(prefix="Goal:")
    text: str = dspy.InputField(format=lambda x: x.upper())
    action: str = dspy.OutputField(prefix="Action:")
```

## Limitations

- Complex nested types require Pydantic models
- Some LLMs struggle with strict type constraints
- Field descriptions and constraints add to prompt length
- Default values only work for InputField, not OutputField

## Official Documentation

- **DSPy Documentation**: https://dspy.ai/
- **DSPy GitHub**: https://github.com/stanfordnlp/dspy
- **Signatures API**: https://dspy.ai/api/signatures/
- **Signatures Guide**: https://dspy.ai/learn/programming/signatures/

Overview

This skill helps design clear, type-safe DSPy signatures to define what a DSPy module expects and returns. It guides creation of inline and class-based signatures, adding InputField/OutputField definitions, type hints, and optional Pydantic models for structured validation.

How this skill works

It inspects a task description and lists of input/output fields and type constraints, then generates a dspy.Signature class (or inline signature) with InputField and OutputField annotations. The skill can include Literal constraints, Optional types, collection hints, Pydantic models for nested structures, and validation constraints like min/max or ge/le. It also produces docstrings and field descriptions to guide model behavior.

When to use it

  • Defining a new DSPy module or wrapping an LLM task with a signature
  • When outputs must be structured, validated, or type-safe
  • Designing multi-field responses or complex input/output relationships
  • Adding Pydantic models to represent nested or record-like outputs
  • Converting informal task descriptions into explicit DSPy inputs/outputs

Best practices

  • Write a descriptive class docstring — it becomes the task instruction
  • Use dspy.InputField.desc and OutputField.desc to guide the model
  • Constrain categorical outputs with typing.Literal for safety
  • Provide sensible defaults for optional inputs; validate outputs in forward()
  • Use Pydantic models for complex nested types and post-run validation

Example use cases

  • Summarization signature: document -> list[str] summary + word_count
  • Entity extraction: text -> List[Entity] where Entity is a Pydantic model
  • Sentiment analysis: text -> sentiment (Literal) + score + aspects + reasoning
  • RAG answer module: context:list[str], question -> answer, confidence, source_passage
  • Multi-label classifier returning categories list and a primary_category

FAQ

Should I use class-based signatures or inline strings?

Use inline signatures for simple inputs/outputs and quick prototypes. Choose class-based signatures when you need multiple fields, type constraints, descriptions, defaults, or Pydantic models for nested structures.

How do I enforce categorical outputs?

Annotate the output with typing.Literal and dspy.OutputField. Literal restricts possible values and helps the model produce constrained categories.