A core tenet of DSPy’s philosophy is that separating your task from its implementation lets you reimplement your program whenever models, techniques, or your data improves.
If a new LLM arrives, you can change your config and run an optimization. When new techniques like RLM or Flex emerge, a one- or two-line update lets you try the new strategy. All the while, your task definition doesn’t change.
This month brought a new model: Jev. Perhaps you’ve heard of it?
Jev isn’t an LLM and it doesn’t output text. The company that made Jev, TypeSafe, calls it a “System One” model, “a new class of frontier models built to make fast, structured decisions that software can use directly.” Think of Jev as a decision engine: given a question, it returns true-or-false, multiple choice, and ranked option answers. In exchange for this limitation, Jev is extremely cheap and fast.
Plenty of people use DSPy to do exactly this! It turns an LLM into a function your software can call directly. If you optimize this function with GEPA or MIPROv2, you can port your program to small, fast models. Jev’s “fast, structured decisions that software can use directly” maps perfectly to this pattern.
TypeSafe’s Diogo Almeida seems to agree:
Today we’re adding System One support to DSPy, so you can define and optimize programs powered by Jev without changing your existing task definitions.
Get System One support. Upgrade DSPy with the TypeSafe extra, using pip or uv:
pip install -U "dspy[typesafe]"
uv add -U "dspy[typesafe]"
Building DSPy Programs with Jev
DSPy’s Predict Module now runs on System One models. Any Signature whose output fields are all decisions works with System One models: a bool, a Literal of fixed options, or one of the new Noul, Score, and Choice types. Input fields can be any type. Each output field also needs a desc phrased as the question you want answered, like “Do I need to reply?”
Consider the following signature I use to process my emails:
import dspy
lm = dspy.LM('openai/gpt-6-luna', api_key=OPENAI_API_KEY)
dspy.configure(lm=lm)
class EmailTriage(dspy.Signature):
"""Triage an email."""
sender: str = dspy.InputField(desc="Name and address")
subject: str = dspy.InputField()
body: str = dspy.InputField(desc="The first 1,000 characters")
history: str = dspy.InputField(desc="Past emails with this sender")
needs_response: bool = dspy.OutputField(desc="Do I need to reply?")
email_sorter = dspy.Predict(EmailTriage)
result = email_sorter(sender="…", subject="…", body="…", history="…")
print(result.needs_response)
A script on my server calls the email_sorter program, using GPT-6 Luna to handle the LM call, and adds a task to my todo manager if a reply is required.
Because this signature has one boolean output field with a description, it’s compatible with System One models as-is. Let’s use Jev to power it:
import dspy
from dspy.experimental import TypeSafe
som = TypeSafe("jev-latest", api_key=TYPESAFE_API_KEY)
dspy.configure(lm=som)
# Same signature
email_sorter = dspy.Predict(EmailTriage)
result = email_sorter(sender="…", subject="…", body="…", history="…")
print(result.needs_response)
We’ve added two lines. From dspy.experimental we imported TypeSafe, then used it to connect to Jev. We think there are going to be tons of System One models (more than there already are!) and we’ll evolve this connector as the field converges on consistent call patterns.
Everything else stays the same. If your Signature has an incompatible output field, DSPy will throw an error and name the field.
Predict prepares the call, executes it, and parses the output as before. Behind the scenes, it treats the bool output field as a Noul, a boolean that carries its probability, asks Jev for the probability it is true, and returns a plain bool. No update to your signature is needed.
Jev, however, passes back some additional information: probability distributions. With a Noul typed output field, DSPy stores the probability that the answer is true in Noul’s probability attribute.
It’s important to me that I don’t miss any important emails (a false positive is annoying, a false negative is potentially perilous), so I’d like my program to be more likely to indicate an email needs a response. I can do this by adjusting the threshold on my output field, the probability at or above which the answer flips from False to True:
import dspy
from dspy.experimental import Noul
class EmailTriage(dspy.Signature):
"""Triage an email."""
sender: str = dspy.InputField(desc="Name and address")
subject: str = dspy.InputField()
body: str = dspy.InputField(desc="The first 1,000 characters")
history: str = dspy.InputField(desc="Past emails with this sender")
needs_response: Noul = dspy.OutputField(desc="Do I need to reply?")
email_sorter = dspy.Predict(EmailTriage)
email_sorter.fields["needs_response"] = {"threshold": 0.3}
result = email_sorter(sender="…", subject="…", body="…", history="…")
print(result.needs_response.probability) # e.g. 0.4
print(result.needs_response.value) # True, because 0.4 > 0.3
The other decision types, Score and Choice, have similar levers, cuts and weights, respectively.
If you call an LM with a Signature containing a Noul, Score, or Choice, DSPy describes those types to the LM and parses its reply. It all just works. You can even run GEPA on these programs.
With these probability parameters, we can define how Jev’s decisions map to our task. Which makes them ideal levers for optimization.
Introducing the ReAnchor optimizer
ReAnchor is an optimizer that fits the numbers that turn System One model probabilities into answers: a threshold for each bool or Noul, cuts for each Score, and weights for each Choice. It works on any Predict-based module with compatible output decision types, including signatures that use plain bool and Literal outputs.
When using an LM to power EmailTriage, I optimized the program using GEPA. Using a sample of my email history as training data, a large LLM iteratively adjusted the program’s instructions, testing each candidate against a metric. In this case, my metric gives a correct “needs reply” twice the credit of a correct “no reply.” I would rather read a few extra emails than miss one I needed to answer.
GEPA does a great job adjusting EmailTriage’s instructions for an LM. But System One models give us a cheaper lever. ReAnchor runs the program on the training set once and caches the probabilities. It then tries a threshold in each gap between the probabilities Jev returned, and keeps a new one only when it improves performance, while preventing overfitting. The search makes no LLM calls and no new Jev calls.
import dspy
from dspy.experimental import ReAnchor
def metric(example, pred, trace=None) -> float:
"""A correct positive counts 2 and a correct negative 1."""
if bool(pred.needs_response) != example.needs_response:
return 0.0
return 2.0 if example.needs_response else 1.0
# split your examples into train and val
optimizer = ReAnchor(metric)
optimized = optimizer.compile(dspy.Predict(EmailTriage), trainset=train, valset=val)
print(optimized.fields["needs_response"]) # {'threshold': 0.24}
The new threshold greatly improves the program. It misses 90% fewer emails I should reply to: 5 instead of 48. In exchange, it flags 47 more emails I could have skipped, which comes to 62 of the 844 emails I didn’t answer.
The Score type rates an input against ordered, descriptive levels. Let’s change my program to rate emails on three levels: “Spam”, “No reply needed”, and “Needs my reply”.
from dspy.experimental import Score
Triage = Score["Spam", "No reply needed", "Needs my reply"]
class EmailTriage(dspy.Signature):
"""Triage an email."""
# Same inputs as before
action: Triage = dspy.OutputField(desc="What should I do with it?")
When queried, Jev returns a probability for each level. DSPy weights each level’s index by its probability and adds them up, which gives a value between 0 and 2. Two cuts divide this range into our three levels, and their defaults are 0.5 and 1.5. ReAnchor, given our metric, moves these cuts to 0.1 and 1.1:
The new cuts put 294 of 336 held-out emails at the right level, up from 239.
ReAnchor’s calibration is incredibly fast and cheap, because it needs no reflection LLM calls. We tested several optimization techniques on System One programs, and calibration proved to be the most effective lever.
Because Signatures with Noul, Score, and Choice output types work with LM calls as well as System One models, you can also use ReAnchor on LM programs.
System One models are new, and more arrive every day. We’re still learning how to use them well, and these first integrations are experimental. They’ve been working great for us, but please file an issue if you hit a bug. The docs for decision types and ReAnchor cover the full API.
We’ll continue building with System One models like Jev, and are especially excited to bring System One support to DSPy’s Flex Module, so optimizers can split your program’s work across LLMs, System One models, and code.