FFFF
Skip to content

Releases: stanfordnlp/dspy

3.3.1

Choose a tag to compare

@isaacbmiller isaacbmiller released this 21 Aug 23:07
Immutable release. Only release title and notes can be modified. E880
638e155

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 ...

Read more

3.3.0

Choose a tag to compare

@isaacbmiller isaacbmiller released this 03 Aug 20:06
Immutable release. Only release title and notes can be modified.
e4e97aa

DSPy 3.3.0

DSPy 3.3.0 is a feature release with a new experimental way to optimize programs as code, a native-tool-aware ReAct implementation, and the next stage of DSPy's move toward a typed, provider-neutral language-model system.

Most existing DSPy programs should keep working without changes. Review the API changes if you construct Image, Audio, or File values from paths or URLs; use NumPy-backed features from the base install; inspect detailed GEPA results; construct code interpreters directly; use RLM(max_iterations=...); consume raw Responses API tool-call outputs; or catch provider-specific LM exceptions.

We would especially appreciate feedback on Flex, ReActV2, and the typed LM path. These APIs expand what DSPy can optimize and how it can connect to model providers, and real-world usage will help shape their next iterations.

Highlights

Flex Optimizes Program Structure, Not Just Prompts — @michaelisaac-dev

Most DSPy modules fix the shape of a program up front: Predict makes one prediction, ReAct runs a tool loop, and RLM runs a code interpreter in a loop. Optimizers can improve the instructions around that structure, but the structure itself stays fixed. The new experimental dspy.Flex moves the implementation into the search space so GEPA can discover the decomposition instead.

Give Flex the same signature you would give Predict and it starts with the simplest working baseline: one dspy.Predict, or one dspy.RLM when tools are supplied. During compilation, GEPA can rewrite the complete module implementation—changing the predictors, control flow, DSPy primitives, and balance between Python and LM calls—against your metric.

program = dspy.Flex("question -> answer")
optimized = dspy.GEPA(metric=metric, reflection_lm=reflection_lm).compile(
    program,
    trainset=trainset,
    valset=valset,
)

print(optimized.module_src)

Optimizer-authored source always runs in a CodeInterpreter sandbox, using dspy.PythonInterpreter by default. Predictor construction and LM calls bridge back to the host, broken candidates score as failures instead of crashing the search, and max_predictor_calls guards against runaway generated programs. Metrics can also accept a program_trace to score how a result was produced—for example, penalizing programs that make too many LM calls.

The optimized module_src is part of the program's serialized state, so saving and loading preserves the implementation GEPA discovered. Flex is experimental, and ordinary GEPA behavior is unchanged when a program does not contain a Flex module.

PR: #10047

ReActV2 and Native Tool-Calling History - @isaacbmiller

dspy.ReActV2 is a new version of ReAct built around native tool calling. It is currently marked as experimental.

The signature now uses dspy.History, dspy.Tool, and dspy.ToolCalls(which can now optionally store dspy.ToolCallResults), rather than the custom next_tool_args and custom trajectory syntax. Using dspy.History also means that messages are now broken up into user/assistant/tool groups rather than one long user message with the trajectory.

This changes the execution model in a few concrete ways:

  • parallel_tool_calls support: DSPy preserves each call/result pair by ID. You can do this in native mode or in non-native mode
  • Multi-turn native tool call support: Prior tool calls and results can be replayed as assistant and tool messages instead of being flattened into prompt text.
  • Each turn lives in dspy.History as structured messages rather than one ever-growing trajectory string, so providers with prompt caching can reuse stable prefixes more effectively. We have seen up to 50% decreases in cost for some tasks when testing this internally.

ReActV2 converts callables to dspy.Tool, adds an internal submit tool for final outputs, handles unknown tools and tool exceptions, accepts serialized history input, and can force final submission when the model does not call submit.

PRs: #9823, #9824, #9825, #9835

Typed, Provider-Neutral LM Boundary - @MaximeRivest

DSPy is moving from an untyped LM boundary based on prompt, messages, and provider-shaped kwargs toward a typed, provider-neutral contract:

def forward(self, request: dspy.LMRequest) -> dspy.LMResponse:
    ...

The resulting API is a cleaner LM extension point:

  • LiteLLM can become an optional compatibility fallback in the planned 3.5+ path, instead of a required part of the core LM contract.
  • Custom LM authors can implement one typed LMRequest -> LMResponse path instead of guessing which OpenAI/LiteLLM-shaped inputs will arrive.
  • Custom LMs can translate between DSPy's typed objects and their own provider, local runtime, gateway, or inference stack.
  • Adapters can start to depend on DSPy's representation of messages, multimodal content, tool calls, reasoning, citations, usage, cache controls, metadata, and stream events.

Most users do not need to change anything in 3.3. Existing lm(...), modules, and programs keep their current behavior by default.

Try out the typed return path with dspy.context(experimental=True), and the public migration plan explains the staged transition for custom LM and adapter authors.

See the full plan here

PRs: #9786, #9802, #9828

BaseLM Runtime, Save/Load, Errors, and LiteLLM Decoupling - @MaximeRivest

BaseLM now owns shared runtime state and supports sanitized state serialization through dump_state() and load_state(). Serialized LM state excludes API keys, preserves legacy saved states, and requires explicit opt-in before importing trusted custom LM classes.

Saved programs with custom LMs are easier to reason about, LM copies isolate DSPy-owned mutable state, and callers can catch dspy.LMError or a narrower DSPy subclass instead of depending on provider-specific exception classes. LiteLLM imports are lazy, which keeps the core LM API less coupled to a specific provider bridge at import time.

PRs: #9752, #9820, #9821, #9826

LM and Responses API Updates Since 3.3.0b1 - @MaximeRivest, @isaacbmiller

Since the beta, DSPy has added an explicit BaseLM.forward() contract, exported the typed LM API, supported typed direct calls through BaseLM.__call__, made optional-provider imports thread-safe, and fixed LM state round trips for GPT-5 models.

The OpenAI Responses path now emits Responses-native tool and tool_choice request shapes. Legacy Responses outputs use the same Chat-style tool-call representation as the Chat Completions path, while typed LMToolCallPart objects preserve raw provider fields.

PRs: #9837, #9840, #9841, #9843, #9877, #9999, #10003, #10014, #10026, #10028

API Changes

Breaking Changes

Resource Construction and Validation No Longer Perform Implicit I/O

Constructing or validating dspy.Image, dspy.Audio, and dspy.File values no longer interprets locator-shaped strings as instructions to read a local file or fetch a remote URL. This prevents LM-output parsing, Pydantic validation, and deserialization from silently granting filesystem or network access merely because a value resembles a path or URL.

Resource loading now requires an explicit factory:

Before 3.3 DSPy 3.3 Behavior
Image(path) or Image(url=path) Image.from_path(path) Read and embed a local image
Image(url, download=True) Image.from_url(url) Download and embed a remote image
Image.from_url(url) or Image.from_url(url, download=False) Image(url) or Image(url=url) Keep a non-downloading provider-fetched URL reference
Audio(path) Audio.from_path(path) Read and embed local audio
Audio(url) Audio.from_url(url) Download and embed remote audio
File(path) File.from_path(path) Read and embed a local file
encode_image(path) Image.from_path(path) Explicitly read a local image
encode_audio(path_or_url) Audio.from_path(path) or Audio.from_url(url) Explicitly load audio
encode_file_to_dict(path) File.from_path(path) Explicitly read a local file

There are several related compatibility changes:

  • Image.from_url() now downloads the resource and returns an embedded data URI. Use Image(url) when the model provider should fetch the reference instead.
  • Image.from_url(..., download=...) and the download_images / verify options on encode_image() were removed. Choose reference construction or an explicit factory instead.
  • Pydantic payloads containing download or verify are rejected without fetching. The deprecated direct developer call Image(url, download=True) remains available with a warning through 3.3.
  • The deprecated compatibility call requires a positional source: Image(url, download=True). Validation-style calls such as Image(url=url, download=True) are rejected.
  • Image.from_file(), Image.from_PIL(), and Audio.from_file() remain as deprecated aliases through 3.3 and are scheduled for removal in 3.4. Use Image.from_path(), Image(pil_image), and Audio.from_path() respectively.
  • Safe in-memory inputs—including data URIs, bytes, PIL images, audio arrays, structured dictionaries, and existing resource instances—remain supported.

The explicit Image.from_url(url, verify=...) and Audio.from_url(url, verify=...) factories still accept TLS certificate verification controls. The removed verify option applies to encode_image().

Image.from_url() and Audio.from_url() make synchronous caller-initiated requests, follow redirects, and do not provide an SSRF allowlist. Applications remain responsible for validating or allowlisting destinations derived from untrusted input.

PR: #10111 by @isaacbmiller

numpy Is Now Optional

numpy is no longer i...

Read more

3.3.0b1

3.3.0b1 Pre-release
Pre-release

Choose a tag to compare

@isaacbmiller isaacbmiller released this 28 May 01:11
Immutable release. Only release title and notes can be modified.
b2829b7

DSPy 3.3.0b1 Release Notes

DSPy 3.3.0b1 is a beta release including a new ReActV2 module, a new BaseLM System, updating to GEPA 0.1.1, and fewer dependencies framework wide.

Most existing DSPy programs should keep working without changes. Review the breaking changes if you depend on NumPy from the base dspy install, inspect GEPA result internals, or catch provider-specific LM exceptions directly.

We would really appreciate feedback on ReActV2 and the new LM system! Please try them out and let us know if you run into any issues.

Highlights

ReActV2 and Native Tool-Calling History - @isaacbmiller

dspy.ReActV2 is a new version of ReAct built around native tool calling. It is currently marked as experimental.

The signature now uses dspy.History, dspy.Tool, and dspy.ToolCalls(which can now optionally store dspy.ToolCallResults), rather than the custom next_tool_args and custom trajectory syntax. Using dspy.History also means that messages are now broken up into user/assistant/tool groups rather than one long user message with the trajectory.

This changes the execution model in a few concrete ways:

  • parallel_tool_calls support: DSPy preserves each call/result pair by ID. You can do this in native mode or in non-native mode
  • Multi-turn native tool call support: Prior tool calls and results can be replayed as assistant and tool messages instead of being flattened into prompt text.
  • Each turn lives in dspy.History as structured messages rather than one ever-growing trajectory string, so providers with prompt caching can reuse stable prefixes more effectively. We have seen up to 50% decreases in cost for some tasks when testing this internally.

ReActV2 converts callables to dspy.Tool, adds an internal submit tool for final outputs, handles unknown tools and tool exceptions, accepts serialized history input, and can force final submission when the model does not call submit.

PRs: #9823, #9824, #9825, #9835

Typed, Provider-Neutral LM Boundary - @MaximeRivest

DSPy is moving from an untyped LM boundary based on prompt, messages, and provider-shaped kwargs toward a typed, provider-neutral contract:

def forward(self, request: dspy.LMRequest) -> dspy.LMResponse:
    ...

The resulting API is a cleaner LM extension point:

  • LiteLLM can become an optional compatibility fallback in the planned 3.5+ path, instead of a required part of the core LM contract.
  • Custom LM authors can implement one typed LMRequest -> LMResponse path instead of guessing which OpenAI/LiteLLM-shaped inputs will arrive.
  • Custom LMs can translate between DSPy's typed objects and their own provider, local runtime, gateway, or inference stack.
  • Adapters can start to depend on DSPy's representation of messages, multimodal content, tool calls, reasoning, citations, usage, cache controls, metadata, and stream events.

Most users do not need to change anything in 3.3. Existing lm(...), modules, and programs keep their current behavior by default.

Try out the typed return path with dspy.context(experimental=True), and the public migration plan explains the staged transition for custom LM and adapter authors.

See the full plan here

PRs: #9786, #9802, #9828

Smaller Base Install - @isaacbmiller

We have been whittling away at dependencies!

The base install is lighter: numpy is now optional via dspy[numpy], and direct dependencies on asyncer, xxhash, and typeguard were removed in favor of standard-library paths.

Users who do not need NumPy-backed retrieval, embeddings, or optimizers get a smaller default install with fewer transitive dependencies. Users who need NumPy-backed features can install dspy[numpy].

PRs: #9659, #9733, #9734, #9735

BaseLM Runtime, Save/Load, Errors, and LiteLLM Decoupling - @MaximeRivest

BaseLM now owns shared runtime state and supports sanitized state serialization through dump_state() and load_state(). Serialized LM state excludes API keys, preserves legacy saved states, and requires explicit opt-in before importing trusted custom LM classes.

Saved programs with custom LMs are easier to reason about, LM copies isolate DSPy-owned mutable state, and callers can catch dspy.LMError or a narrower DSPy subclass instead of depending on provider-specific exception classes. LiteLLM imports are lazy, which keeps the core LM API less coupled to a specific provider bridge at import time.

PRs: #9752, #9820, #9821, #9826

Custom Objects for RLM Sandboxes - @kmad

dspy.RLM can now accept custom sandbox-serializable values through SandboxSerializable.

Users can pass richer objects, such as DataFrames, into the sandbox with explicit setup, serialization, assignment, and preview behavior instead of forcing everything through prompt text.

PRs: #9411

GEPA 0.1.1 Support - @BenMcH

DSPy now supports gepa[dspy]==0.1.1, including updated DspyGEPAResult behavior, tests, and docs.

Users can move to the current GEPA DSPy integration in this beta. The breaking changes below call out the result-shape changes for code that inspects detailed GEPA outputs.

PRs: #9673

Breaking Changes

numpy Is Now Optional

numpy is no longer installed with base dspy. Features that need NumPy now require the numpy extra:

pip install "dspy[numpy]"

Affected areas include embeddings, KNN/KNNFewShot, SIMBA, and other NumPy-backed optimizer or retrieval paths. (#9659 by @isaacbmiller< 10BC0 /a>)

GEPA Result Shapes Changed With gepa[dspy]==0.1.1

The upstream GEPA 0.1.1 API changed several result structures, and DspyGEPAResult now mirrors those shapes. Users who inspect optimized_program.detailed_results may need to update code:

  • DspyGEPAResult.candidates is now a list of compiled DSPy modules, not instruction dictionaries.
  • DspyGEPAResult.best_candidate now returns a compiled DSPy module.
  • val_subscores is now list[dict[Any, float]], keyed by validation instance id.
  • per_val_instance_best_candidates is now dict[Any, set[int]].
  • best_outputs_valset is now dict[Any, list[tuple[int, Prediction]]] when tracked.
  • highest_score_achieved_per_val_task now returns a dictionary keyed by validation instance id.

If you pass custom GEPA reflection templates directly, note that GEPA 0.1.1 renamed default placeholders from <curr_instructions> / <inputs_outputs_feedback> to <curr_param> / <side_info>. In dspy.GEPA, passing reflection_prompt_template through gepa_kwargs now raises a clear ValueError; use instruction_proposer for custom proposal behavior instead. (#9673 by @BenMcH)

LM Error Types Are Now DSPy-Normalized

LM failures are now mapped into DSPy exception classes. This should make LM error handling more consistent, but code that catches provider-specific or LiteLLM-specific errors directly may need to catch dspy.LMError or a narrower DSPy subclass. (#9826 by @MaximeRivest)

LM Runtime Changes

  • Added core typed LM objects for provider-neutral requests, responses, messages, parts, tool specs, reasoning config, cache config, usage, history entries, stream events, and stream assembly. (#9786 by @MaximeRivest)
  • Added OpenAI/LiteLLM compatibility conversion helpers so current provider-shaped calls can move through LMRequest and LMResponse internally. (#9802 by @MaximeRivest)
  • Routed adapter __call__ and acall through the normalized LM boundary while converting back to legacy parser inputs for compatibility. (#9802 by @MaximeRivest)
  • Current BaseLM calls still receive OpenAI/LiteLLM-shaped kwargs. Internally, adapters now move through adapter messages -> LMRequest -> OpenAI/LiteLLM kwargs -> current BaseLM -> LMResponse -> existing adapter postprocess path. (#9802 by @MaximeRivest)
  • Added exact adapter-format regression coverage before and during the boundary work, including Chat, JSON, XML, BAML, TwoStep, tools, reasoning, citations, multimodal content, custom types, demos, and history. (#9791, #9792 by @MaximeRivest)
  • Made BaseLM the owner of shared runtime state and changed BaseLM.copy() to an explicit shallow runtime copy. (#9821 by @MaximeRivest)
  • Added sanitized BaseLM.dump_state() and BaseLM.load_state() support, including trusted custom LM class loading through allow_unsafe_lm_state=True. (#9820 by @MaximeRivest)
  • Added structured DSPy LM exceptions and wired them into dspy.LM and adapter fallback behavior. (#9826 by @MaximeRivest)
  • Made LiteLLM imports lazy so importing DSPy does not eagerly import the LiteLLM bridge. (#9752 by @MaximeRivest)
  • Added the public typed LM API migration plan for custom LM and adapter authors. (#9828 by @MaximeRivest)

ReActV2 and Tool Calling

  • Added dspy.ReActV2, a native-tool-aware ReAct predictor with dspy.Tool conversion, an internal submit tool, serialized history input support, unknown-tool and tool-exception handling, and forced final submission when needed. (#9825 by @isaacbmiller)
  • Enabled ReActV2-style agents to use provider-side parallel tool calls when the adapter is configured with parallel_tool_calls, preserving each model-requested call and each observation by call ID. (#9823, #9824, #9825 by @isaacbmiller)
  • Moved ReActV2 history from a formatted trajectory string into structured dspy.History, so native-tool providers can see prior turns as assistant/tool messages and prompt-caching providers can reuse stable prompt prefixes more effectively. (#9824, #9825 by @isaacbmiller)
  • Preserved provider tool-call IDs on ToolCalls.ToolCall and added ToolCallResults for call IDs, tool names, values, and error flags. (#9823 by @isaacbmiller)
  • Taught adapters to replay prior native assistant tool calls and matching tool results as native LM messages when native function calling is enabled, wi...
Read more

3.2.1

Choose a tag to compare

@isaacbmiller isaacbmiller released this 05 May 19:37
Immutable release. Only release title and notes can be modified.
29448ae

DSPy 3.2.1 Changelog

Bug fixes

  • Fixed async streaming LM calls so custom headers are forwarded to LiteLLM streaming completions. (#9669)
  • Fixed dspy.Embedder so per-call caching=False is honored for both sync and async embedding calls. (#9708)

Documentation

  • Moved Deployment into the technical documentation tabs and promoted production use cases under Community. (#9709)
  • Updated the production use-cases copy for DSPy.
  • Fixed MkDocs admonition rendering in Deployment and Observability docs. (#9690, #9691)
  • Fixed duplicate-word typos across docs, source, and tests. (#9695)

CI and release

  • Hardened TestPyPI release validation by adding strict twine check and reducing unused workflow permissions. (#9648)
  • Gave TestPyPI publishing its own GitHub environment. (#9649)
  • Removed an unused setup-node step from the docs push workflow. (#9702)
  • Replaced direct merge-to-main after publishing with a version bump PR. (#9716)
  • Refreshed uv.lock for the 3.2.0 release state. (#9650)

Dependency updates

  • Updated test and development dependencies: pytest-asyncio, ruff, pre-commit, datamodel-code-generator, optuna, and urllib3. (#9665, #9664, #9699, #9701, #9697, #9698)
  • Updated documentation dependencies: mkdocs-llmstxt, mkdocstrings, mistune, and mkdocs-jupyter. (#9661, #9668, #9666, #9700)
  • Updated GitHub Actions dependencies: actions/cache and astral-sh/setup-uv. (#9662, #9663)

Contributors

Excluded

  • The GEPA 0.1.1 result/documentation update was intentionally excluded from this release. (#9673)

3.2.0

D63E

Choose a tag to compare

@isaacbmiller isaacbmiller released this 21 Apr 17:17
Immutable release. Only release title and notes can be modified.
d3a890c

Highlights

BetterTogether Allows Chaining Optimizers — @dilarasoylu

BetterTogether now accepts arbitrary optimizers as keyword arguments and chains them via strategy strings. For example, BetterTogether(metric=m, p=GEPA(...), w=BootstrapFinetune(...)) with strategy="p -> w -> p" will prompt-optimize, fine-tune, then prompt-optimize again -- evaluating each step on a valset and returning the best program. (#9149)

There are many promising strategies that may come from running multiple GEPA steps in sequence, or combining prompt and weight optimization steps in sequence, and we are excited to see what the community comes up with.

Beginning of decoupling DSPy from LiteLLM — @MaximeRivest

@MaximeRivest has an ongoing effort to decouple DSPy from LiteLLM, making it much easier to use custom LMs with DSPy. In this release, adapters no longer import litellm at all -- BaseLM now exposes capability properties (supports_function_calling, supports_reasoning, supports_response_schema, supported_params) and a new dspy.ContextWindowExceededError replaces the litellm error throughout. Custom BaseLM backends can now integrate with DSPy's retry/truncation logic without any litellm dependency. (#9516, #9521, #9522)

Warning on Input field type mismatch — @michaelisaac-dev

Passing a value that doesn't match a signature's declared type now logs a warning (using typeguard). Extra fields not in the signature also warn. Disable with dspy.configure(warn_on_type_mismatch=False). (#9313)

Hardened RLM and PythonInterpreter — @isaacbmiller

Tool calls now use kwargs-only dispatch, the JS tool bridge returns structured errors instead of throwing (preventing Deno crashes), and stdout parsing skips non-JSON lines instead of crashing. Subprocess restarts now correctly replay tool/mount registration. (#9341, #9351)

Notices

Restricted pickle for disk cache

We have added an opt-in dspy.configure_cache(restrict_pickle=True) that swaps pickle.load with a restricted unpickler that only allows litellm/openai types, numpy reconstruction helpers, and user-registered safe_types. Prevents arbitrary code execution from corrupted or malicious cache files. (#9629)

In a future release, we will make this restriction the default behavior.

optuna is now optional

Moved from a required dependency to pip install dspy[optuna]. Saves ~12.7 MB. Only MIPROv2 and BootstrapFewShotWithOptuna use it. (#9397)


All Changes

Features

Bug Fixes

  • fix(adapters): skip JSON schema for DSPy custom types in prompt by @darinkishore (#9257)
  • fix: ColBERTv2RetrieverLocal forward method variable scoping bug by @veeceey (#9272)
  • fix(predict): remove code corruption in ProgramOfThought._parse_code by @adityasingh2400 (#9276)
  • fix(RLM): show head and tail for RLM outputs by @isaacbmiller (#9282)
  • fix(rlm): change repl_variable preview to 1000 chars by @isaacbmiller (#9296)
  • fix(dspy): SemanticF1 and CompleteAndGrounded now return dspy.Prediction by @Copilot (#9302)
  • refactor(rlm): enhance code fence parsing and REPL entry formatting by @isaacbmiller (#9309)
  • Fix false rollout_id warning when LM temperature is unset by @okhat (#9316)
  • fix: pass metric_threshold to BootstrapFewShot for unshuffled case by @kvr06-ai (#9317)
  • fix: only suggest reducing valset in GEPA when it is large by @bledden (#9320)
  • fix: make DummyLM delegate to forward() instead of overriding call by @bledden (#9322)
  • fix: run evaluation on main thread when num_threads=1 by @zamal-db (#9328)
  • fix(saving): block unsafe LM loading keys by @isaacbmiller (#9334)
  • fix(settings): add allow_pickle flag to settings loading by @isaacbmiller (#9339)
  • fix(interpreter): Harden JSONRPC communication and tool bridge by @isaacbmiller (#9341)
  • fix(interpreter): reset tools/mounts when Deno subprocess restarts by @isaacbmiller (#9351)
  • fix(adapters): pass use_native_function_calling in JSONAdapter.acall by @isaacbmiller (#9374)
  • fix(adapters): raise AdapterParseError on empty LM response instead of silent None by @isaacbmiller (#9389)
  • Deprecation warning for prefix, format, and parser kwargs in InputField/OutputField by @MaximeRivest (#9394)
  • fix(signature): reject duplicate input and output field names by @MaximeRivest (#9432)
  • fix(adapter): guard annotation subclass checks for ReAct tool args by @isaacbmiller (#9433)
  • fix(JSONAdapter): Ensure JSON serialization handles diacritics correctly for Pydantic BaseModel by @matrn (#9493)
  • Restrict litellm version to <=1.82.6 by @isaacbmiller (#9498)
  • fix(deps): pin typeguard==4.4.3 to prevent supply chain attacks by @isaacbmiller (#9551)
  • fix(lm): preserve per-message structure in Responses API conversion by @lawrence3699 (#9580)
  • fix: cloudpickle serialization of Signature on Python 3.14 by @isaacbmiller (#9616)
  • fix: correct demo index assignment in MIPROv2 raw_chosen_params by @Ricardo-M-L (#9627)
  • Fix cache bugs: os.fspath(None) crash, double disk lookups, lazy logging by @isaacbmiller (#9628)
  • Add restrict_pickle option for safe disk cache deserialization by @isaacbmiller (#9629)
  • Revert "fix: set LITELLM_LOCAL_MODEL_COST_MAP before litellm import to avoid HTTP fetch" by @isaacbmiller (#9637)
  • fix(base_lm): guard response.usage access to handle missing usage field by @isaacbmiller (#9638)

Refactors

  • Refactor: Make DummyLM and callbacks depend on BaseLM instead of LM by @MaximeRivest (#9515)
  • Refactor: Move model capability checks to BaseLM to remove litellm from adapters by @MaximeRivest (#9516)
  • Refactor: Introduce DSPy-owned ContextWindowExceededError to decouple error handling by @MaximeRivest (#9521)
  • refactor: type-hint adapters with BaseLM instead of LM by @MaximeRivest (#9522)

Security

Testing

Docs

CI / Infrastructure

Dependency updates (Dependabot)

  • orjson 3.11.2 -> 3.11.8 (#9597)
  • requests 2.32.4 -> 2.33.1 (#9603)
  • anthropic 0.54.0 -> 0.89.0 (#9599)
  • tqdm 4.67.1 -> 4.67.3 (#9623)
  • pre-commit 4.2.0 -> 4.5.1 (#9622)
  • denoland/setup-deno 2.0.3 -> 2.0.4 (#9596)
  • actions/checkout 3.6.0 -> 6.0.2 (#9595)
  • actions/setup-node 3.9.1 -> 6.3.0 (#9600)
  • stefanzweifel/git-auto-commit-action 5.2.0 -> 7.1.0 (#9598)
  • pypa/gh-action-pypi-publish 1.13.0 -> 1.14.0 (#9618)
  • mkdocs-jupyter 0.25.1 -> 0.26.1 (#9605)
  • mkdocstrings 0.29.0 -> 1.0.3 (#9609)
  • mkdocs-material 9.6....
Read more

3.1.3

Choose a tag to compare

@okhat okhat released this 05 Feb 16:18
4ef729d

What's Changed

RLMs

GEPA

  • Update gepa[dspy] dependency to version 0.0.26 adding support for cached evals in GEPA expected to reduce metric calls, fix MLFlow logging, and bug fixes by @LakshyAAAgrawal in #9238
  • Update gepa[dspy] version to 0.0.25 adding python==3.14 support by @LakshyAAAgrawal in #9224
  • Temporarily remove the GEPA tool optimization doc by @chenmoneygithub in #9220
  • fix(gepa): Remove enable_tool_optimization feature by @Ju-usc in #9223

Maintenance

Deferred to a later release (added then reverted before cutting this release):

New Contributors

Full Changelog: 3.1.2...3.1.3

3.1.2

Choose a tag to compare

@okhat okhat released this 19 Jan 14:16
64528a3

What's Changed

Maintenance

New Contributors

Full Changelog: 3.1.1...3.1.2

3.1.1

Choose a tag to compare

@okhat okhat released this 19 Jan 02:30
b65bf37

What's Changed

RLMs

  • feat(rlm): Add RLM Module and improve PythonInterpreter by @isaacbmiller in #9193
  • Use language in system instructions for dspy.Code fields by @mariusarvinte in #9106
  • Update dspy.RLM to improve reliability and avoid pydantic warnings by @okhat in #9210
  • fix(RLM): Change FinalAnswerResult to FinalOutput and remove RLM call by @isaacbmiller in #9212

GEPA

  • Fix GEPAFeedbackMetric Protocol missing 'self' parameter by @Copilot in #9111
  • feat(gepa): add tool description optimization for multi-agent systems by @Ju-usc in #8928
  • Update pyproject.toml to update gepa from 0.0.22 -> 0.0.24 by @LakshyAAAgrawal in #9161
  • Patch dspy.gepa to handle list[dict] output from a dspy.LM by @mariusarvinte in #9169

Adapters

  • Upgrade json_repair to fix parsing issue of underscore-style float number by @chenmoneygithub in #9088
  • Properly yield the last chunk in streaming by @chenmoneygithub in #9089
  • Avoid unwanted exception chaining when running async tool in sync context by @stevapple in #9092
  • Enhance StreamListener to support generic type annotations for output by @TomeHirata in #9112
  • FIX: streamify was appending StatusStreamingCallback directly to the shared settings.callbacks list by @glesperance in #9073
  • fix(BAMLAdapter): Use docstrings to describe BaseModels by @BenMcH in #9125 D63E
  • Fix Responses API structured outputs by @Olocool17 in #9130

Maintenance

  • Fix UsageTracker AttributeError when using ParallelExecutor with dspy.context by @Copilot in #9095
  • Update uv.lock dspy version to match the latest release by @chenmoneygithub in #9067
  • [docs] Add Google-style docstrings for dspy/adapters/chat_adapter.py ChatAdapter class #9063 by @azai91 in #9072
  • Fix ContextWindowExceededError after 3 retries in react loop by @Copilot in #9110
  • fix: skip merging when both usage entry values are None by @chizukicn in #9121
  • Fix: enforce positive memory_max_entries for in-memory cache by @mshr-h in #9128
  • Add typing to dspy.datasets.dataset by @max-muoto in #9143
  • Increase max_iters for dspy.ReAct by @chenmoneygithub in #9162
  • fix(dspy): prevent argument injection in LocalProvider subprocess calls by @Copilot in #9160
  • fix(dspy): populate InputField default values in Predict by @ritsuki1227 in #9167
  • fix(PythonInterpreter): Remove overly permissive read permissions and add strict allow by @isaacbmiller in #9081
  • Add save / load methods for DSpy settings by @WeichenXu123 in #9165

Docs

New Contributors

Full Changelog: 3.1.0...3.1.1

3.1.0

Choose a tag to compare

@chenmoneygithub chenmoneygithub released this 06 Jan 18:48

What's Changed

This is a 3.1.0 official release. We are making the beta release 3.1.0beta1 official.

Optimizers & Evaluation

Features & Enhancements

Security & Serialization

Bug Fixes & Type Handling

  • Fix TypeError when tracking usage with Anthropic models returning Pydantic objects by @Copilot in #8978
  • Update old Anthropic model names by @TomeHirata in #8992
  • fix(XMLAdapter): Implement user message formatting by @BenMcH in #9003
  • Fix content input conversion for OpenAI Responses API by @Copilot in #8993
  • Refactor: update type hints for adapter and LM methods by @TomeHirata in #9025
  • fix(dspy): exclude gpt-5-chat from reasoning model classification by @mindful-time in #9033
  • fix(dspy): Example.toDict() fails to serialize dspy.History objects by @Copilot in #9047
  • Some continuous format fix by @chenmoneygithub in #8987

Documentation & Tutorials

  • Add documentation for provider-side prompt caching with Anthropic and OpenAI by @Copilot in #8970
  • [docs] Add Google-style docstrings for dspy/evaluate/metrics.py by @eramis73 in #8954
  • fix: broken PyPI downloads badge from pepy.tech in README and docs home page by @dushmanta05 in #8995
  • Document ToolCall.execute() availability from dspy 3.0.4b2 by @Copilot in #9004
  • fix(docs): add python language id to code block by @Ahmad8864 in #9023
  • docs: add note on Python version for pre-commit by @akshatvishu in #9028
  • chore(docs): update dspy.settings.configure and dspy.settings.context to dspy.configure and dspy.context by @isaacbmiller in #9060
  • docs: add documentation for async tool usage and error handling by @TomeHirata in #9054

Minor Fixes, Maintenance & CI

New Contributors

Full Changelog: 3.0.4...3.1.0b1

3.1.0b1

3.1.0b1 Pre-release
Pre-release

Choose a tag to compare

@chenmoneygithub chenmoneygithub released this 18 Nov 00:26
a5671ef

What's Changed

This is a pre-release for 3.1.0.

Optimizers & Evaluation

Features & Enhancements

Security & Serialization

Bug Fixes & Type Handling

  • Fix TypeError when tracking usage with Anthropic models returning Pydantic objects by @Copilot in #8978
  • Update old Anthropic model names by @TomeHirata in #8992
  • fix(XMLAdapter): Implement user message formatting by @BenMcH in #9003
  • Fix content input conversion for OpenAI Responses API by @Copilot in #8993
  • Refactor: update type hints for adapter and LM methods by @TomeHirata in #9025
  • fix(dspy): exclude gpt-5-chat from reasoning model classification by @mindful-time in #9033
  • fix(dspy): Example.toDict() fails to serialize dspy.History objects by @Copilot in #9047
  • Some continuous format fix by @chenmoneygithub in #8987

Documentation & Tutorials

  • Add documentation for provider-side prompt caching with Anthropic and OpenAI by @Copilot in #8970
  • [docs] Add Google-style docstrings for dspy/evaluate/metrics.py by @eramis73 in #8954
  • fix: broken PyPI downloads badge from pepy.tech in README and docs home page by @dushmanta05 in #8995
  • Document ToolCall.execute() availability from dspy 3.0.4b2 by @Copilot in #9004
  • fix(docs): add python language id to code block by @Ahmad8864 in #9023
  • docs: add note on Python version for pre-commit by @akshatvishu in #9028
  • chore(docs): update dspy.settings.configure and dspy.settings.context to dspy.configure and dspy.context by @isaacbmiller in #9060
  • docs: add documentation for async tool usage and error handling by @TomeHirata in #9054

Minor Fixes, Maintenance & CI

New Contributors

Full Changelog: 3.0.4...3.1.0b1

0