Skip to main content

Vanilla-Python ergonomics on top of DSPy - write plain functions/dataclasses and get DSPy modules

Project description

FunnyDSPy [Experimental 🧪]

Vanilla-Python ergonomics on top of DSPy

I love DSPy, but I could not wrap my head around the fact that I have to write such a function-y Class which I could not use it as a normal Python function, with a 'normal' return value. So I wrote this library as an experiment to see if I could make it easier to use DSPy (for me!).

This library is a thin wrapper around DSPy that allows you to write plain functions and dataclasses and get DSPy modules and normal Python return values automatically. If you need the original DSPy Prediction (for optimization or loss computation), just call the function with _prediction=True.

Example 1:

import funnydspy as fd
import dspy

# Configure your language model
dspy.configure(lm=dspy.LM('openai/gpt-4.1-nano'))#, api_key='YOUR_OPENAI_API_KEY'))

@fd.ChainOfThought
def rag(query: str, context: str) -> str: return answer

# Get Python objects directly
answer = rag("What is the capital of France?", "France is a country in Europe.")
# → "The capital of France is Paris."

# Get DSPy Prediction for optimization
pred = rag("What is the capital of France?", "France is a country in Europe.", _prediction=True)
# → dspy.Prediction(reasoning="...", answer="The capital of France is Paris.")

Example 2:

import funnydspy as fd
import dspy
from typing import NamedTuple

# Configure your language model
dspy.configure(lm=dspy.LM('openai/gpt-4.1-nano'))

@fd.ChainOfThought
def analyze(numbers: list[float], threshold: float) -> tuple[float, list[float]]:
    """Analyze numbers and return statistics."""
    class Stats(NamedTuple):
        mean: float # The average of the numbers
        above: list[float] # Numbers above the threshold
    return Stats

# Get Python objects directly
mean_val, above_vals = analyze([1, 5, 3, 8, 2], 4.0)
# → (4.0, [5.0, 8.0])

# Get DSPy Prediction for optimization
pred = analyze([1, 5, 3, 8, 2], 4.0, _prediction=True)
# → dspy.Prediction(reasoning="...", mean=4.0, above=[5.0, 8.0])

✨ Features

  • 🐍 Pythonic: Write normal Python functions with type hints
  • 📦 Structured Returns: Use dataclasses, NamedTuples, or tuples for complex outputs
  • 🔄 DSPy Compatible: Seamlessly integrates with DSPy optimization and chaining
  • 📝 Smart Documentation: Extracts descriptions from docstrings and inline comments
  • 🎯 Type Safe: Automatic type conversion between LM strings and Python types
  • ⚡ Multiple Modules: Support for Predict, ChainOfThought, ReAct, and custom modules

📦 Installation

pip install funnydspy

🚀 Quick Start

Basic Usage with Dataclasses

from dataclasses import dataclass
from typing import List
import funnydspy as fd
import dspy

dspy.configure(lm=dspy.LM('openai/gpt-4.1-nano'))

@dataclass
class Stats:
    mean_value: float      # The average of all numbers
    above_threshold: List[float]  # Values greater than threshold

@fd.Predict
def analyze_data(numbers: List[float], threshold: float) -> Stats:
    """Analyze a list of numbers and return statistics."""
    return Stats

# Use it like a normal Python function
result = analyze_data([1, 5, 3, 8, 2], 4.0)
print(result.mean_value)        # 3.8
print(result.above_threshold)   # [5.0, 8.0]

Tuple Returns with Variable Names

@fd.ChainOfThought
def summarize_text(text: str) -> tuple[str, int, List[str]]:
    """Summarize text and extract key information."""
    summary = "A concise summary of the text"
    word_count = "Total number of words"
    key_points = "List of main points"
    return summary, word_count, key_points

summary, count, points = summarize_text("""Modules help you describe AI behavior as code, not strings.
To build reliable AI systems, you must iterate fast. But maintaining prompts makes that hard: it forces you to tinker with strings or data every time you change your LM, metrics, or pipeline. Having built over a dozen best-in-class compound LM systems since 2020, we learned this the hard way—and so built DSPy to decouple AI system design from messy incidental choices about specific LMs or prompting strategies.""")

Using Different DSPy Modules

# Chain of Thought reasoning
@fd.ChainOfThought
def complex_reasoning(problem: str) -> str:
    """Solve a complex problem step by step."""
    return solution

# Basic prediction
@fd.Predict
def simple_task(input_text: str) -> str:
    """Perform a simple text transformation."""
    return output

Working with Optimizers

# Access the underlying DSPy module for optimization
optimizer = dspy.BootstrapFewShot(metric=your_metric)
compiled_analyze = optimizer.compile(analyze_data.module, trainset=your_data)

# Wrap the optimized module back into a Pythonic interface
analyze_optimized = fd.funnier(compiled_analyze)

# Use the optimized version with the same interface
result = analyze_optimized([1, 5, 3, 8, 2], 4.0)

📚 Documentation

Decorators

  • @fd.Predict - Basic prediction module
  • @fd.ChainOfThought - Chain of thought reasoning

Return Types

FunnyDSPy supports various return type patterns:

  1. Dataclasses: Structured data with field descriptions
  2. NamedTuples: Lightweight structured returns
  3. Tuples: Simple multiple returns with automatic field naming
  4. Primitives: Single values (str, int, float, etc.)

Type Conversion

FunnyDSPy automatically handles conversion between LM string outputs and Python types:

  • strstr (passthrough)
  • intint (parsed)
  • floatfloat (parsed)
  • boolbool (true/false/yes/no/1/0)
  • List[T]List[T] (JSON or comma-separated)
  • Dict[K, V]Dict[K, V] (JSON parsed)

Documentation Extraction

FunnyDSPy extracts field descriptions from multiple sources:

@dataclass
class Result:
    """Result container."""
    value: float  # The computed value
    status: str   # Processing status

@fd.Predict
def process(
    data: List[float],  # Input data to process
    mode: str          # Processing mode
) -> Result:
    """
    Process data and return results.
    
    Parameters
    ----------
    data: The input dataset
    mode: How to process the data
    
    Returns
    -------
    Result.value: The final computed value
    Result.status: Success or error status
    """
    return Result

🔧 Advanced Usage

Custom DSPy Modules

# Register custom DSPy modules
class CustomModule(dspy.Module):
    def __init__(self, signature):
        super().__init__()
        self.predictor = dspy.Predict(signature)
    
    def forward(self, **kwargs):
        return self.predictor(**kwargs)

fd.register(CustomModule, alias="custom")

@fd.custom
def my_function(input_text: str) -> str:
    return output

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built on top of the excellent DSPy framework
  • Inspired by the need for more Pythonic LM programming interfaces

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

funnydspy-0.2.0.tar.gz (16.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

funnydspy-0.2.0-py3-none-any.whl (13.1 kB view details)

Uploaded Python 3

File details

Details for the file funnydspy-0.2.0.tar.gz.

File metadata

  • Download URL: funnydspy-0.2.0.tar.gz
  • Upload date:
  • Size: 16.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.7

File hashes

Hashes for funnydspy-0.2.0.tar.gz
Algorithm Hash digest
SHA256 11e8f423b85a64221c2a0312758bf06e96c4df41d306aa28cd6c9b74599d8665
MD5 c2f1e0300bacd49a0bd765e9adc46a7d
BLAKE2b-256 167936242ef4825588c0977183a4645e4d88e3db1ee247497e5fc2b45cb1df4c

See more details on using hashes here.

File details

Details for the file funnydspy-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: funnydspy-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 13.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.7

File hashes

Hashes for funnydspy-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f89dbbf68e3b67e2b346b2538b778bd1a2d996d7e683125fc5f744bab293f3ee
MD5 b86ced00a3563b21d2f75ed2b1a5e390
BLAKE2b-256 9799419f2a08c65f0da9c342b92259db7c477f524dac420904dcd1c610859869

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page