DSPy 0-to-1 Guide

repository·main·Indexed 42 days ago

https://github.com/evalops/dspy-0to1-guide

A comprehensive tutorial and implementation guide for DSPy (Declarative Self-improving Python). It covers core concepts including Signatures, Modules, and Optimizers, providing a learning path from beginner to advanced levels. The guide includes practical examples for RAG pipelines, persona-driven agents, Pydantic validation, and GEPA optimization, as well as production infrastructure integration with Docker, Prometheus, and Grafana.

Tokens
9.8K
Snippets
28
Records
35
Agent score
53%

What's inside DSPy 0-to-1 Guide

  1. How Signatures, Modules, and Optimizers work together

    main

    DSPy's architecture is built on three core abstractions that decouple task logic from prompt engineering:

    1. Signatures: Declarative task specifications. They define the input/output behavior (the 'what') without specifying the prompt (the 'how'). They act as a contract for modules.
    2. Modules: Composable building blocks that encapsulate prompting strategies (e.g., Predict, ChainOfThought, ReAct). Modules are composed like neural network layers to build complex pipelines.
    3. Optimizers: Self-improving engines (e.g., BootstrapFewShot). They take a module, a dataset, and a metric, then iteratively refine prompts and few-shot examples to maximize performance.
  2. Advanced DSPy features and capabilities

    main

    Beyond basic modules, DSPy supports several advanced patterns for production-grade LLM applications:

    • Reflective Prompt Evolution (GEPA): A technique where the model reflects on its own prompts to iteratively improve them.
    • Reinforcement Learning optimization: An experimental optimizer that uses RL signals to explore the prompt space.
    • Pydantic Integration: Use Pydantic models to enforce strict output schemas and ensure runtime data safety.
    • Parallel & Asynchronous Execution: Support for parallel LLM calls and asynchronous pipelines to reduce latency.
    • Production Infrastructure: Includes dspy.Cache to reduce costs by caching LM responses, as well as logging and observability modules for debugging intermediate states.
  3. Optimize DSPy pipelines with automatic optimizers

    main

    DSPy uses optimizers (also known as teleprompters) to automatically improve your prompts based on data. An optimizer like BootstrapFewShot takes your module and a training set, then automatically generates prompt variations, tests them against a metric, and selects the best performing version (often including few-shot examples).

    from dspy.teleprompt import BootstrapFewShot
    from dspy import metrics
    
    # Automatic optimization using a teleprompter
    teleprompter = BootstrapFewShot(metric=metrics.answer_exact_match)
    optimized_analyzer = teleprompter.compile(
        ContractAnalyzer(), 
        trainset=contract_examples
    )
  4. Use DSPy Modules to encapsulate prompting strategies

    main

    A Module encapsulates a specific prompting pattern or reasoning strategy. You can use built-in modules or create custom ones by subclassing dspy.Module and implementing a forward method.

    Built-in Modules:

    • dspy.Predict: Basic prompting based on a signature.
    • dspy.ChainOfThought: Implements step-by-step reasoning (automatically includes reasoning steps in the prompt).
    • dspy.ProgramOfThought: Generates and executes code to solve problems.
    • dspy.ReAct: Implements Reasoning + Acting, allowing the model to use a list of tools during its reasoning process.
    • dspy.Retrieve: A module for retrieving relevant context.
    class ReasoningQA(dspy.Module):
        def __init__(self):
            super().__init__()
            self.cot = dspy.ChainOfThought("question -> answer")
        
        def forward(self, question: str):
            # Automatically includes reasoning steps
            return self.cot(question=question)
  5. What are DSPy Signatures and how to define them

    main

    A Signature is a declarative specification of a task's input/output behavior. It acts as an API contract for LLM interactions, defining what the task is without specifying how the model should do it.

    You can define signatures in two ways:

    1. String-based: A simple "input_name -> output_name" syntax.
    2. Class-based: A more detailed approach using dspy.Signature where you define fields using dspy.InputField and dspy.OutputField. This allows for type hints, descriptions, and constraints.
    import dspy
    
    # String-based signature (simple)
    qa_signature = "question -> answer"
    
    # Class-based signature (detailed)
    class QuestionAnswering(dspy.Signature):
        """Answer questions accurately based on given context."""
        question: str = dspy.InputField(desc="User's question")
        context: str = dspy.InputField(desc="Relevant background information")
        answer: str = dspy.OutputField(desc="Accurate, concise answer")
  6. Understand the DSPy Declarative Programming Paradigm

    main

    DSPy replaces manual prompt engineering with a declarative approach. Instead of writing long, brittle string prompts, you define Signatures to specify what the task is (inputs and outputs) and Modules to specify how the logic is composed. This provides type safety, better readability, and makes your logic reusable across different LLM models.

    import dspy
    
    # Declarative: Define the task via a Signature
    class ContractAnalysis(dspy.Signature):
        """Extract key legal terms from contracts"""
        contract_text: str = dspy.InputField(desc="Raw contract text")
        payment_terms: str = dspy.OutputField(desc="Payment terms and deadlines")
        termination: str = dspy.OutputField(desc="Termination conditions")
        liability: str = dspy.OutputField(desc="Liability limitations")
    
    # Composable: Define the logic via a Module
    class ContractAnalyzer(dspy.Module):
        def __init__(self):
            super().__init__()
            self.analyze = dspy.ChainOfThought(ContractAnalysis)
        
        def forward(self, contract_text: str):
            return self.analyze(contract_text=contract_text)
  7. Best practices for building with DSPy

    main

    To build robust self-improving applications, follow these guidelines:

    • Start simple: Begin with a single module (like Predict or ChainOfThought) and add complexity incrementally.
    • Collect representative examples: Optimizers require high-quality, representative training data. Label your examples carefully.
    • Separate training and evaluation: Always use distinct train and test sets to prevent overfitting prompts to your training examples.
    • Manage cost and latency: Optimization involves many LLM calls. Use smaller models and enable caching during the experimentation phase.
    • Validate outputs: For structured tasks, integrate Pydantic or explicit parsing to ensure outputs meet your required schema.
  8. Configure the Language Model in DSPy

    main

    Use dspy.configure(lm=...) to set the global language model for your program. You can use cloud providers like OpenAI or local providers via Ollama.

    For OpenAI: dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))

    For local Ollama models: dspy.configure(lm=dspy.LM('ollama_chat/llama3'))

    import dspy
    
    dspy.configure(lm=dspy.LM('openai/gpt-4o-mini'))
  9. Install DSPy and set up local LLMs

    main

    To use DSPy, install the core package via pip. If you prefer to run models locally for privacy or cost reasons, you can use Ollama. After installing Ollama and pulling a model (e.g., llama3), configure DSPy using the ollama_chat/ prefix.

    # Install DSPy
    pip install dspy-ai
    
    # Optional: Local LLM setup (MacOS example)
    brew install ollama
    ollama pull llama3
    ollama serve
  10. Learning Path for DSPy Mastery

    main

    The guide is structured into three progressive learning stages:

    Beginner (1-2 hours)

    Focus on understanding the motivation, core concepts (Signatures, Modules, Optimizers), and basic installation. Use the examples in examples/basic/.

    Intermediate (3-4 hours)

    Focus on pipeline composition (RAG, summarization), evaluation metrics, optimization using teleprompters, and building agents with tools.

    Advanced (5+ hours)

    Deep dive into advanced patterns like GEPA, Pydantic validation, persona-driven use cases, and production infrastructure integration (Prometheus, Grafana, Docker).

  11. Evaluate a DSPy program with metrics

    main

    To measure performance, follow these steps:

    1. Prepare a dataset: Create a list of dspy.Example objects. Use .with_inputs() to specify which fields are inputs.
    2. Choose a metric: Use built-in metrics like metrics.answer_exact_match or write a custom function that returns a score in [0, 1].
    3. Run evaluation: Use dspy.Evaluate to run your module against the dataset.
    from dspy import Example, Evaluate, metrics
    
    # 1. Prepare dataset
    train_examples = [
        Example(question="What is the capital of France?", answer="Paris").with_inputs("question"),
        Example(question="Who wrote 1984?", answer="George Orwell").with_inputs("question"),
    ]
    
    # 2. Evaluate
    evaluate = Evaluate(
        trainset=train_examples,
        metric=metrics.answer_exact_match,
    )
    
    # Assuming 'rag' is your module
    result = evaluate(rag)
    print("Accuracy:", result)
  12. Optimize modules using DSPy Optimizers (Teleprompters)

    main

    Optimizers (also known as teleprompters) automatically improve your modules by generating better prompts and few-shot examples based on provided training data and a metric.

    Key Optimizers:

    • BootstrapFewShot: Creates effective few-shot examples by bootstrapping from successful demonstrations. Use max_bootstrapped_demos and max_labeled_demos to control density.
    • COPRO (Coordinate Ascent Prompt Optimization): An advanced optimizer that tries multiple prompt candidates over several iterations.
    • BetterTogether: Used for jointly optimizing multiple modules within a complex pipeline.

    Custom Metrics:

    You can define a custom function to act as a metric. The function should take (prediction, ground_truth) and return a score (e.g., a float representing similarity or accuracy).

    from dspy.teleprompt import BootstrapFewShot
    from dspy import metrics, Example
    
    # Prepare training data
    train_examples = [
        Example(question="What is the capital of France?", answer="Paris").with_inputs("question"),
        Example(question="Who wrote Romeo and Juliet?", answer="William Shakespeare").with_inputs("question"),
    ]
    
    # Create and configure optimizer
    teleprompter = BootstrapFewShot(
        metric=metrics.answer_exact_match,
        max_bootstrapped_demos=5,
        max_labeled_demos=3
    )
    
    # Original module
    qa_module = SimpleQA()
    
    # Optimized version
    optimized_qa = teleprompter.compile(qa_module, trainset=train_examples)