github stanfordnlp/dspy 3.3.1

2 hours ago

DSPy 3.3.1

DSPy 3.3.1 contains many interpreter fixes and improvements. It makes PythonInterpreter
easier to install, substantially strengthens sandbox isolation and request
handling, and adds end-to-end visibility into interpreter execution. The release
also improves optimizer throughput, adapter correctness, and MCP compatibility.

Highlights

PythonInterpreter: Managed Runtime, Hardening, and Lifecycle Visibility

Installation, isolation, and execution integrity

PythonInterpreter now has an optional managed runtime installation:

pip install "dspy[deno]"

DSPy prefers that managed binary when present, while continuing to support
system Deno 2.x and an explicit custom deno_command. The default path pins
Pyodide, validates Deno >=2.0.0,<3.0.0, and ignores ambient Node and Deno
project configuration so nearby application files cannot change sandbox startup.

The interpreter also closes several execution-integrity and isolation gaps:

  • unsolicited sandbox diagnostics can no longer desynchronize JSON-RPC replies;
  • request IDs are unpredictable, and recursive execution through one of an
    interpreter's own host tools is rejected;
  • bundled runtime files are protected and Deno-cache access is revoked after
    startup;
  • mounted files with distinct host paths cannot silently collide at the same
    sandbox basename; and
  • guest code cannot change host-tool identity by mutating JavaScript globals or
    prototypes.

Observability and agent integration

DSPy's callback API now exposes the complete interpreter lifecycle:

  • interpreter execution start and end;
  • sandbox-to-host tool-call start and end;
  • interpreter process startup and shutdown.

Events retain callback ancestry across modules, interpreters, tools, and LM
calls. End callbacks receive terminating BaseException values such as
cancellation and interruption instead of incorrectly reporting those operations
as successful. Optimizer compile() runs receive the same start/end coverage.

PythonInterpreter.execution_instructions now gives RLM an accurate description
of the Pyodide environment, including state persistence and unavailable native
process capabilities. This helps generated code use the sandbox correctly.

Typing, serialization, and compatibility details

  • Tool defaults and NoneType annotations serialize correctly across the
    sandbox boundary.
  • CodeInterpreterError is now a DSPyError while retaining its existing
    RuntimeError compatibility.
  • LM-facing execution errors use one consistent formatter across interpreter and
    agent modules.
  • Existing system Deno and custom-command integrations remain supported; the
    managed runtime is opt-in.

PRs: #10119,
#10120,
#10134,
#10135,
#10136,
#10186,
#10190,
#10194,
#10205,
#10206,
#10208, and
#10255

Faster Multi-Proposal GEPA Optimization

DSPy now uses GEPA 0.1.4 and supports its multi-proposal sampling, selection,
acceptance, tracking, and checkpoint-state contracts through gepa_kwargs.
DSPy's adapter saves and restores its random-number-generator state, making
resumed proposal sampling consistent with uninterrupted optimization.

When a sampling strategy produces multiple candidates, DSPy can evaluate those
candidates concurrently. Candidate-level and example-level concurrency share the
existing num_threads budget rather than multiplying it. For example, four
candidates evaluated with num_threads=8 receive two example workers each; total
DSPy-controlled concurrency remains eight.

import dspy
from gepa.strategies.proposal_sampling import IndependentSampling
from gepa.strategies.proposal_selection import BestImprovement

optimizer = dspy.GEPA(
    metric=metric,
    max_metric_calls=2_000,
    reflection_lm=dspy.LM("openai/gpt-5", temperature=1.0, max_tokens=32_000),
    num_threads=8,
    gepa_kwargs={
        "sampling_strategy": IndependentSampling(4),
        "selection_strategy": BestImprovement(),
        "acceptance_criterion": "strict_improvement",
    },
)

The default single-proposal strategy retains its previous execution shape.
max_reflection_cost is not yet supported by DSPy's GEPA adapter and now raises
clearly when set instead of silently providing an ineffective budget.

Metrics can also report named objective_scores and select an objective-aware
frontier through gepa_kwargs. Objective, hybrid, and cartesian frontiers let
GEPA use dimensions such as quality, privacy, or cost when selecting parents and
merges, while the scalar metric continues to gate acceptance and select the final
program. Tracked results expose aggregate objective scores, best candidates per
objective, and each objective's independently achieved maximum.

GEPA 0.1.4 has a known upstream limitation: it requests traces while evaluating
accepted candidates on the full validation set. For programs whose traced and
ordinary evaluation paths differ after a runtime failure, this can cause missing
or misaligned validation results. The upstream correction is targeted for GEPA
0.1.5.

PRs: #10209,
#10210,
#10259

More Reliable Structured Adapter Outputs

When an LM omits an output field with a declared default, default factory, or
None-allowing annotation, ChatAdapter, JSONAdapter, XMLAdapter, and adapters
built on them now apply the declared fallback. Missing required outputs continue
to raise AdapterParseError.

This also prevents adapter fallback merely because a provider omitted a native
optional output.

XMLAdapter now formats and parses nested Pydantic models, typed dictionaries,
lists, mappings, nullable fields, and unions as nested XML. It continues to
accept the previous JSON-inside-an-outer-XML-field representation for backward
compatibility.

PRs: #10148,
#10239

MCP SDK v2 Compatibility and Structured Results

DSPy's MCP bridge supports both MCP SDK v1 and v2 field names, v1
ClientSession, and the v2 high-level Client. The default tool-result semantics
are unchanged: historical text and non-text content remains authoritative rather
than being replaced by v2 structured content.

Applications can now opt into machine-readable MCP results:

tool = dspy.Tool.from_mcp_tool(client, mcp_tool, result_mode="structured")

Structured mode returns structuredContent exactly when the server supplies it,
including arrays, scalar values, empty values, and explicit JSON null. It falls
back to the existing content conversion when structured content is absent. DSPy
does not infer, parse, or unwrap the returned value.

PRs: #10188,
#10235

API and Compatibility Changes

CodeAct and ProgramOfThought Deprecation

dspy.CodeAct and dspy.ProgramOfThought now emit DeprecationWarning when
constructed. They are scheduled for removal in DSPy 3.5; use dspy.RLM for new
code.

PR: #10198

Resource Download Timeouts

Image.from_url() and Audio.from_url() now default to a 30-second request
timeout instead of potentially waiting forever:

image = dspy.Image.from_url(url, timeout=60)
audio = dspy.Audio.from_url(url, timeout=60)

Pass timeout=None to retain the previous unbounded behavior.

PR: #10149

Typed Interpreter and ReAct Errors

CodeInterpreterError is now also a DSPyError while retaining
RuntimeError compatibility. ReAct preserves ContextWindowExceededError after
trajectory truncation is exhausted, and LM-facing execution errors now use one
consistent formatter.

PRs: #10134,
#10132,
#10135,
#10139

Additional Fixes

  • ParallelExecutor correctly treats a completed task returning None as
    complete. #10142
  • COPRO.compile(..., eval_kwargs=None) now matches its documented optional
    contract. #10087
  • Dataset.reset_seeds() now honors valid zero-valued sizes and seeds.
    #9906

Full PR List

GEPA and Optimizers

Callbacks and Parallel Execution

Adapters, Tools, and MCP

Agents, Errors, and Code Execution

Datasets

  • Honor zero-valued sizes and seeds in Dataset.reset_seeds by @alvinttang
    (#9906).

Documentation

Testing, CI, Dependencies, and Release Engineering

  • Update main's version metadata after the 3.3.0 release
    (#10127).
  • Repair dead settings ownership assertions by @isaacbmiller
    (#10131).
  • Cap FastAPI for LiteLLM proxy compatibility in the development environment by
    @daleselaji-dev (#10151).
  • Reuse one LiteLLM test server per pytest worker by @isaacbmiller
    (#10154).
  • Use a memory-only cache by default in tests by @isaacbmiller
    (#10156).
  • Remove wall-clock retry waits from tests by @isaacbmiller
    (#10157).
  • Run extra test suites in parallel by @isaacbmiller
    (#10164).
  • Prefetch the Ollama image during test setup by @isaacbmiller
    (#10169).
  • Regenerate the project lockfile by @isaacbmiller
    (#10199).
  • Update the documentation build's urllib3 dependency
    (#9931).
  • Update GitHub Actions checkout
    (#9945).
  • Update GitHub Actions Python setup
    (#9965).
  • Update GitHub Actions caching
    (#9966).
  • Update the zizmor security-analysis action
    (#9967).
  • Update the documentation build's mkdocstrings-python dependency
    (#9968).
  • Update the actionlint workflow action
    (#9988).
  • Update the documentation build's Mistune dependency
    (#10109).
  • Update the documentation build's MkDocs Material dependency
    (#10110).

Contributors

Thank you to @alvinttang, @daleselaji-dev, @immu4989, @isaacbmiller,
@katherineahn, @michaelisaac-dev, @saime428, and @yzxcj797 for contributing to
this release.

Full Changelog: 3.3.0...3.3.1

Don't miss a new dspy release

NewReleases is sending notifications on new releases.