typesafeify: a Typesafe decorator for DSPy
September 15, 2026 ยท View on GitHub
This is a deliberately stripped-down proof-of-concept fork of
DSPy. It exists to test one idea in
isolation: can a normal DSPy signature opt into Typesafe's typed inference path
with one decorator, while application code keeps constructing and calling
dspy.Predict exactly as before?
The meaningful integration diff is intentionally tiny:
from typing import Literal
import dspy
+from typesafe_dspy import typesafeify
+@typesafeify(score_fields={"severity_score": [1, 5]})
class SupportTicketTriage(dspy.Signature):
ticket: dict = dspy.InputField()
customer_impacting: bool = dspy.OutputField()
owner_team: Literal["api-platform", "billing", "checkout", "infra", "support-ops"] = dspy.OutputField()
severity_score: float = dspy.OutputField()
internal_summary: str = dspy.OutputField()
The caller does not learn a new predictor API:
predictor = dspy.Predict(SupportTicketTriage)
result = predictor(**ticket_context)
What happens after adding the decorator
@typesafeify(...) reads the signature's output annotations and builds a
hybrid execution plan.
flowchart LR
A[Same DSPy signature] --> B{Output annotation}
B -->|bool, Literal, configured score| C[One Typesafe request]
B -->|freeform string| D[DSPy generative LM]
C -->|trusted typed results| D
C --> E[Same DSPy Prediction]
D --> E
| Signature output | Plain DSPy | With @typesafeify(...) |
|---|---|---|
customer_impacting, needs_human_now | Generated by the DSPy LM | Typesafe Noul probabilities, thresholded into bool |
owner_team, severity_band | Generated by the DSPy LM | Typesafe Choice decisions with per-option probabilities |
severity_score | Generated by the DSPy LM | Typesafe Score, mapped back onto the declared 1โ5 scale |
internal_summary | Generated by the DSPy LM | Generated by the DSPy LM after the five typed results are known |
The demo keeps separate baseline and decorated signature files only to make a controlled before/after run possible. Before sending either request, it proves that both signatures have identical fields and instructions.
Run the comparison
Install the published Typesafe SDK and this fork's development dependencies:
uv sync --extra typesafe
Set OPENAI_API_KEY and TYPESAFE_API_KEY, then run the same three tickets
through both paths. Luna uses medium reasoning by default.
OPENAI_MODEL=gpt-5.6-luna \
uv run python examples/typesafe_dspy_ticket_triage/run_demo.py \
--limit 3 \
--show-document
The command prints the execution plan, exact signature-parity check, typed results and probabilities, field-by-field output differences, freeform text, timing, token usage, and total modeled cost for every case.
Before/after benchmark
Across the three observed cases, the decorated path averaged 1.958 seconds versus 2.329 seconds for plain DSPy: 15.9% faster, or a projected 37.1 seconds saved per 100 sequential calls. Average modeled cost fell from $0.000377 to $0.000263 per ticket, a 30.1% reduction.
The cost calculation will use these explicit inputs:
| Input | Rate | Status |
|---|---|---|
| Typesafe input tokens | $0.042 / 1M tokens | Supplied estimate; replace with a public pricing source when available |
| GPT-5.6 Luna input tokens | $0.20 / 1M tokens | Published OpenAI rate |
| GPT-5.6 Luna output and reasoning tokens | $1.20 / 1M tokens | Published OpenAI rate |
The modeled totals use every priced category in the table: Luna input, Luna output plus reasoning, and Typesafe input. Typesafe output-token usage is reported by the demo but is not assigned a cost because the supplied Typesafe rate covers input tokens only.
Proof-of-concept layout
typesafe_dspy/hybrid.py: decorator, signature planning, hybrid execution, and result metadataexamples/typesafe_dspy_ticket_triage/: runnable controlled comparisontests/typesafe_dspy/test_hybrid.py: behavior and score-scale preservation tests
This fork is an experiment, not a replacement distribution for upstream DSPy. For DSPy itself, use the upstream project and documentation.