A project outgrows you the moment the first contributor merges to main. Stefan Froelich added DSPy.rb’s Gemini adapter in v0.20.0, including text generation, inline images, usage metadata, streaming transport, and structured responses.
This guide describes the adapter that exists now. Model availability and limits belong to Google’s documentation and can change independently of DSPy.rb.
Install and Configure
Add the core and Gemini adapter gems:
gem "dspy"
gem "dspy-gemini"
Set GEMINI_API_KEY, then configure a model using the gemini/ provider prefix:
DSPy.configure do |config|
config.lm = DSPy::LM.new(
"gemini/gemini-2.5-flash",
api_key: ENV.fetch("GEMINI_API_KEY")
)
end
DSPy.rb passes the model name to the Gemini client. Choose a model currently available to your Google AI account.
Typed Prediction
A signature defines the input and the result your Ruby code expects. The adapter constructs the provider request; Predict coerces and validates the returned fields.
class ContentAnalysis < DSPy::Signature
input do
const :content, String, description: "Text to analyze"
end
output do
const :summary, String, description: "A concise summary"
const :topics, T::Array[String], description: "Main topics in the text"
const :confidence, Float, description: "Confidence from 0.0 to 1.0"
end
end
analysis = DSPy::Predict.new(ContentAnalysis).call(content: article)
puts analysis.summary
puts analysis.topics.inspect
Typed output validation catches missing or incompatible fields. It does not establish that a summary is accurate or that a confidence value is calibrated; those are evaluation questions.
Images
Gemini accepts inline image data through DSPy::Image. The current adapter supports JPEG, PNG, GIF, and WebP data up to DSPy.rb’s 5 MB input limit. It does not fetch image URLs.
class InspectImage < DSPy::Signature
input do
const :question, String
const :image, DSPy::Image
end
output do
const :answer, String
end
end
image = DSPy::Image.new(
data: File.binread("diagram.png").bytes,
content_type: "image/png"
)
result = DSPy::Predict.new(InspectImage).call(
question: "Which component owns retries?",
image: image
)
For several images, use an array in the signature:
class CompareImages < DSPy::Signature
input { const :images, T::Array[DSPy::Image] }
output { const :differences, T::Array[String] }
end
Whether a particular Gemini model accepts the media type and count remains a provider constraint.
Structured Outputs
Gemini’s adapter can use provider-native structured output when initialized with structured_outputs: true:
lm = DSPy::LM.new(
"gemini/gemini-2.5-flash",
api_key: ENV.fetch("GEMINI_API_KEY"),
structured_outputs: true
)
predictor = DSPy::Predict.new(ContentAnalysis)
result = DSPy.with_lm(lm) do
predictor.call(content: article)
end
The schema converter supports the subset covered by DSPy.rb’s Gemini specs, including nested structs, enums, arrays, nilable fields, and unions. Provider schema support can be narrower than Sorbet’s type system. Test the signatures you deploy.
Raw Responses and Streaming
raw_chat returns the accumulated text as a String:
lm = DSPy::LM.new(
"gemini/gemini-2.5-flash",
api_key: ENV.fetch("GEMINI_API_KEY")
)
text = lm.raw_chat([
{ role: "user", content: "Explain Fiber-local storage in two sentences." }
])
puts text
The return value does not expose the adapter’s internal response object. Subscribe to lm.tokens for usage plus provider and model attribution:
subscription_id = DSPy.events.subscribe("lm.tokens") do |_event_name, attributes|
puts "Tokens: #{attributes[:total_tokens]}"
puts "Provider: #{attributes['gen_ai.system']}"
puts "Model: #{attributes['gen_ai.request.model']}"
end
lm.raw_chat([{ role: "user", content: "Explain Fiber-local storage." }])
DSPy.events.unsubscribe(subscription_id)
With observability enabled, LM spans also record the completion, requested model, response model when available, and token usage. Gemini-specific finish reasons and safety ratings are not part of the public raw_chat return value.
The Gemini transport streams by default outside ordinary VCR recordings. When a block is supplied in array mode, the adapter yields provider chunks and still accumulates the final response:
text = lm.raw_chat([{ role: "user", content: "Count to five." }]) do |chunk|
pp chunk
end
Those callbacks expose provider chunks, not partial typed predictions. DSPy.rb validates a signature result after the complete response arrives.
Provider Boundaries
- Gemini has no separate system role in this adapter; system messages are sent as user content.
- Inline image data is supported; image URLs are not.
- The adapter records safety ratings and finish reasons internally when Gemini returns them, but
raw_chatdoes not expose that metadata. - Generation options accepted by
DSPy::LM.neware passed to the adapter constructor only if that constructor declares them. The current Gemini constructor exposesstructured_outputs, not a general generation-parameter API.
Keep the task in the signature, keep model-specific behavior at the adapter boundary, and evaluate the complete program against examples that resemble its actual inputs.