DSPy.rb v0.20.0 added three pieces of infrastructure that had previously required application code: a Gemini adapter, fiber-local LM overrides, and storage for optimized program state.

Google Gemini

Stefan Froelich contributed the Gemini adapter. A signature and module can move between supported providers without changing their task contract:

class AnswerQuestion < DSPy::Signature
  input { const :question, String }
  output { const :answer, String }
end

gemini = DSPy::LM.new("gemini/gemini-1.5-flash")
openai = DSPy::LM.new("openai/gpt-4o-mini")

predictor = DSPy::Predict.new(AnswerQuestion)

gemini_answer = DSPy.with_lm(gemini) do
  predictor.call(question: "Explain Ruby fibers")
end
openai_answer = DSPy.with_lm(openai) do
  predictor.call(question: "Explain Ruby fibers")
end

The adapter supports text, inline images, usage metadata, and DSPy.rb’s structured response path. Provider capabilities still differ; changing the model does not make the providers identical.

Fiber-Local LM Overrides

DSPy.with_lm temporarily overrides the configured model for the current fiber. Nested overrides restore the previous model when the block exits, including after an exception.

fast_lm = DSPy::LM.new("openai/gpt-4o-mini")
review_lm = DSPy::LM.new("anthropic/claude-3-5-sonnet-latest")

draft = DSPy.with_lm(fast_lm) { writer.call(topic: "Ruby fibers") }
review = DSPy.with_lm(review_lm) { critic.call(text: draft.text) }

The override isolates configuration. It does not create concurrent work; the surrounding Ruby program still decides what runs and when.

Program Storage

Optimization is expensive enough that throwing away the result is a poor default. DSPy::Storage::ProgramStorage writes serializable program state and optimization metadata to JSON:

storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage")

saved = storage.save_program(
  optimized_program,
  optimization_result,
  metadata: { dataset: "support-v2" }
)

loaded = storage.load_program(saved.program_id)
program = loaded.program

Deserialization requires the saved program class to be loaded and to implement .from_h. The storage serializer captures a small common state for compatible built-in programs; it cannot infer arbitrary state from a custom Ruby object.

MIPROv2 Repairs

This release also fixed failures in the MIPROv2 path and made the optimizer usable against DSPy.rb modules. As always, an optimizer still needs representative examples and a metric that expresses acceptable behavior. A successful run is evidence for that dataset and metric, not a transferable quality guarantee.

Upgrade

bundle update dspy

Install the provider gem you use, such as dspy-gemini, alongside the core dspy gem.