typesafeify: a Typesafe decorator for DSPyThis 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)
@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.
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.
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.
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 testsThis fork is an experiment, not a replacement distribution for upstream DSPy. For DSPy itself, use the upstream project and documentation.
(top 30 of 407)
Hacker News (1)
Python
99.7%
typesafeify: a Typesafe decorator for DSPyThis 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)
@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.
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.
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.
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 testsThis fork is an experiment, not a replacement distribution for upstream DSPy. For DSPy itself, use the upstream project and documentation.
Hacker News (1)
(top 30 of 407)
Python
99.7%