Defining Signatures and Modules
Signature syntax, ChainOfThought, ReAct, and custom DSPy modules.
Defining Signatures and Modules is a free AI Prompt Engineering lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Signatures Are Typed Contracts
A DSPy signature is a Python class that declares the inputs and outputs of a reasoning step. Think of it as a typed function contract for your LLM call.
The docstring becomes the task description. Field annotations tell DSPy what to produce. You never write the actual prompt text — DSPy derives it from this declaration.
Defining a Basic Signature
A minimal signature subclasses dspy.Signature and annotates fields as InputField or OutputField. The class docstring provides the task instruction.
import dspy
class QASignature(dspy.Signature):
"""Answer the question based on the given context."""
context: str = dspy.InputField(desc='Relevant background text')
question: str = dspy.InputField(desc='The question to answer')
answer: str = dspy.OutputField(desc='A concise answer')
# Inspect what DSPy sees
print(QASignature.instructions) # The docstring
print(list(QASignature.input_fields.keys())) # ['context', 'question']
print(list(QASignature.output_fields.keys())) # ['answer']Multiple Output Fields
Signatures can have multiple output fields. DSPy will prompt the model to produce all of them in a single call. This is useful for extracting structured information.
import dspy
class EntityExtraction(dspy.Signature):
"""Extract named entities from the text."""
text: str = dspy.InputField()
people: list[str] = dspy.OutputField(desc='List of person names mentioned')
organizations: list[str] = dspy.OutputField(desc='List of organization names')
locations: list[str] = dspy.OutputField(desc='List of place names')
extractor = dspy.Predict(EntityExtraction)
result = extractor(text='Elon Musk founded SpaceX in Hawthorne, California.')
print(result.people, result.organizations, result.locations)The Predict Module
dspy.Predict is the simplest module. It takes a signature and directly asks the LM to produce the output. No reasoning scaffolding is added — just a structured prompt matching the signature.
Use Predict when the task is straightforward and doesn't require explicit reasoning steps.
import dspy
class Classify(dspy.Signature):
"""Classify the sentiment of the review."""
review: str = dspy.InputField()
sentiment: str = dspy.OutputField(desc='positive, negative, or neutral')
# Predict wraps the signature with a direct prompt
classifier = dspy.Predict(Classify)
result = classifier(review='The food was amazing and the service was excellent!')
print(result.sentiment) # positiveThe ChainOfThought Module
dspy.ChainOfThought augments the signature with an intermediate reasoning field. The model first writes out its reasoning, then produces the final answer.
This consistently improves accuracy on multi-step problems without you writing any chain-of-thought prompt instructions.
import dspy
class MathSolver(dspy.Signature):
"""Solve the math problem."""
problem: str = dspy.InputField()
answer: str = dspy.OutputField(desc='The numerical answer')
# ChainOfThought adds a 'reasoning' step automatically
solver = dspy.ChainOfThought(MathSolver)
result = solver(problem='If a train travels 60 mph for 2.5 hours, how far does it go?')
print(result.reasoning) # Step-by-step reasoning
print(result.answer) # 150 milesThe ReAct Module
dspy.ReAct implements the Reason + Act loop. The model alternates between reasoning steps and tool calls, making it ideal for agents that need to search, calculate, or fetch data.
You provide a list of tools (Python functions with docstrings), and DSPy handles the interleaving automatically.
import dspy
def search_web(query: str) -> str:
"""Search the web and return relevant results."""
# In production, call a real search API
return f'Search results for: {query}'
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression."""
return str(eval(expression))
class ResearchQA(dspy.Signature):
"""Answer the question using web search and calculation as needed."""
question: str = dspy.InputField()
answer: str = dspy.OutputField()
agent = dspy.ReAct(ResearchQA, tools=[search_web, calculate])
result = agent(question='What is 15% of 847?')
print(result.answer)Composing Modules into Programs
Real power comes from composing modules into multi-step programs. Subclass dspy.Module, define sub-modules in __init__, and implement forward() to wire them together.
import dspy
class RetrieveAndAnswer(dspy.Module):
def __init__(self):
super().__init__()
self.retrieve = dspy.Retrieve(k=3) # Retrieves top-3 passages
self.generate = dspy.ChainOfThought('context, question -> answer')
def forward(self, question):
passages = self.retrieve(question).passages
context = '\n'.join(passages)
return self.generate(context=context, question=question)
# This is a complete RAG pipeline in ~10 lines
rag = RetrieveAndAnswer()
result = rag(question='What are the main causes of climate change?')
print(result.answer)Inline Signature Shorthand
For simple cases, DSPy accepts an inline string signature: 'input1, input2 -> output1, output2'. This is convenient for quick prototyping without defining a full class.
import dspy
# Full class signature
class Translate(dspy.Signature):
"""Translate text to French."""
text: str = dspy.InputField()
translation: str = dspy.OutputField()
# Equivalent inline shorthand
translator_v1 = dspy.Predict(Translate)
translator_v2 = dspy.Predict('text -> translation') # Less metadata
# Both work the same way
result = translator_v1(text='Hello world')
print(result.translation)Field Descriptions Matter
The desc parameter in InputField and OutputField is included in the generated prompt. Good descriptions guide the model precisely.
Think of desc as the field-level documentation that appears in the prompt alongside the field name.
import dspy
class Summarize(dspy.Signature):
"""Summarize the article for a busy executive."""
article: str = dspy.InputField(
desc='The full article text to summarize'
)
summary: str = dspy.OutputField(
desc='3-5 bullet points highlighting key decisions and numbers'
)
confidence: float = dspy.OutputField(
desc='Your confidence in the summary accuracy from 0.0 to 1.0'
)
summarizer = dspy.Predict(Summarize)
# DSPy constructs a prompt using all the desc values automaticallySaving and Loading Programs
After optimization, you save the compiled program so you don't need to re-run optimization every time. DSPy serializes the optimized state to a JSON file.
import dspy
class QA(dspy.Signature):
"""Answer questions accurately."""
question: str = dspy.InputField()
answer: str = dspy.OutputField()
program = dspy.ChainOfThought(QA)
# After optimization, save the compiled state
program.save('optimized_qa.json')
# Load it later without re-running optimization
loaded_program = dspy.ChainOfThought(QA)
loaded_program.load('optimized_qa.json')
result = loaded_program(question='What is the speed of light?')
print(result.answer)Assertions for Output Constraints
DSPy Assertions let you declare constraints on outputs. If the model violates the constraint, DSPy automatically retries with corrective feedback — no manual retry logic needed.
import dspy
class ShortAnswer(dspy.Signature):
"""Answer in at most 10 words."""
question: str = dspy.InputField()
answer: str = dspy.OutputField()
class ConstrainedQA(dspy.Module):
def __init__(self):
super().__init__()
self.predict = dspy.Predict(ShortAnswer)
def forward(self, question):
result = self.predict(question=question)
# Assert: answer must be at most 10 words
dspy.Assert(
len(result.answer.split()) <= 10,
'The answer must be 10 words or fewer.'
)
return resultKnowledge Check: ChainOfThought
What does dspy.ChainOfThought add compared to dspy.Predict when using the same signature?
Recap: Signatures and Modules
DSPy signatures are typed class declarations that describe what an LLM step should do — inputs, outputs, and a docstring instruction. Modules like Predict, ChainOfThought, and ReAct implement different reasoning strategies around a signature. You compose multiple modules inside a dspy.Module subclass to build multi-step pipelines. Field desc values guide the model within the auto-generated prompt, and Assertions enforce output constraints with automatic retries.
Frequently asked questions
Is the “Defining Signatures and Modules” lesson free?
Yes — the full text of “Defining Signatures and Modules” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.
What will I learn in “Defining Signatures and Modules”?
Signature syntax, ChainOfThought, ReAct, and custom DSPy modules. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Prompt Engineering?
No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Defining Signatures and Modules” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Prompt Engineering lesson?
Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Introduction to DSPy Framework
- Defining Signatures and Modules
- Compiling and Optimizing Prompts
- Evaluating DSPy Pipelines