dspy 0.28.0 → 0.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df0e7cf901df85567e1553a3d7df8659a71f254fa5671803ea98f0573de0c39a
4
- data.tar.gz: b2d5d37cb05678f97143a1463410d92848ba10178bd50d60ee384827e9efe9b1
3
+ metadata.gz: 8b377060443eeb9c3c5d975e76750d6c519d1b93cf6f20dc6ad20bcda08d1ca4
4
+ data.tar.gz: 4580845b3fd9991b531c8c2bd809595cbd3328da57a23e58307cdbe52e3822bc
5
5
  SHA512:
6
- metadata.gz: 0d870f2d338fcdce0540143decd3674e94a9c24e5fad796536772e6c8064af75dcafc36533bf39a0a1eeefac30777de1d6541a3ab57eb2568a007260d7a5d7a3
7
- data.tar.gz: 13f92278a81ca870f90b662fa40972e41aa6203d27a8792d3d03c13b97d971d7684a0f661dfb86bf03ef5c58e4a8f2f57c2ac48414ed9b8f8e5f0bce7628f231
6
+ metadata.gz: 444a5e08364b2e996bf49d230cb9a94a1930e26d2ea796cd0104ea4888b45ade4099cec502b92c08752631adff47b1d9d1897da9209e9faabbb28fb6761935b0
7
+ data.tar.gz: c1b3a83482c861923304c463d6f0b1c9042fbc31da920526ec18ed0f5148b4408a9023d312eded9263ca2d706e4779b78526e28dc50a17458cbbac9afa56b024
@@ -11,29 +11,32 @@ module DSPy
11
11
  extend T::Sig
12
12
 
13
13
  # Models that support structured outputs (JSON + Schema)
14
- # Based on official Google documentation (Sept 2025)
14
+ # Based on official Google documentation: https://ai.google.dev/gemini-api/docs/models/gemini
15
+ # Last updated: Oct 2025
16
+ # Note: Gemini 1.5 series deprecated Oct 2025
15
17
  STRUCTURED_OUTPUT_MODELS = T.let([
16
- # Gemini 1.5 series
17
- "gemini-1.5-pro",
18
- "gemini-1.5-pro-preview-0514",
19
- "gemini-1.5-pro-preview-0409",
20
- "gemini-1.5-flash", # ✅ Now supports structured outputs
21
- "gemini-1.5-flash-8b",
22
18
  # Gemini 2.0 series
23
19
  "gemini-2.0-flash",
24
- "gemini-2.0-flash-001",
25
- # Gemini 2.5 series
20
+ "gemini-2.0-flash-lite",
21
+ # Gemini 2.5 series (current)
26
22
  "gemini-2.5-pro",
27
- "gemini-2.5-flash",
28
- "gemini-2.5-flash-lite"
23
+ "gemini-2.5-flash",
24
+ "gemini-2.5-flash-lite",
25
+ "gemini-2.5-flash-image"
29
26
  ].freeze, T::Array[String])
30
27
 
31
- # Models that do not support structured outputs (legacy only)
28
+ # Models that do not support structured outputs or are deprecated
32
29
  UNSUPPORTED_MODELS = T.let([
33
- # Legacy Gemini 1.0 series only
34
- "gemini-pro",
30
+ # Legacy Gemini 1.0 series
31
+ "gemini-pro",
35
32
  "gemini-1.0-pro-002",
36
- "gemini-1.0-pro"
33
+ "gemini-1.0-pro",
34
+ # Deprecated Gemini 1.5 series (removed Oct 2025)
35
+ "gemini-1.5-pro",
36
+ "gemini-1.5-pro-preview-0514",
37
+ "gemini-1.5-pro-preview-0409",
38
+ "gemini-1.5-flash",
39
+ "gemini-1.5-flash-8b"
37
40
  ].freeze, T::Array[String])
38
41
 
39
42
  sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T::Hash[Symbol, T.untyped]) }
@@ -111,7 +114,13 @@ module DSPy
111
114
  case property_schema[:type]
112
115
  when "string"
113
116
  result = { type: "string" }
114
- result[:enum] = property_schema[:enum] if property_schema[:enum]
117
+ # Gemini responseJsonSchema doesn't support const, so convert to single-value enum
118
+ # See: https://ai.google.dev/api/generate-content#FIELDS.response_json_schema
119
+ if property_schema[:const]
120
+ result[:enum] = [property_schema[:const]]
121
+ elsif property_schema[:enum]
122
+ result[:enum] = property_schema[:enum]
123
+ end
115
124
  result
116
125
  when "integer"
117
126
  { type: "integer" }
@@ -102,11 +102,6 @@ module DSPy
102
102
  type: "tool",
103
103
  name: "json_output"
104
104
  }
105
-
106
- # Update last user message
107
- if messages.any? && messages.last[:role] == "user"
108
- messages.last[:content] += "\n\nPlease use the json_output tool to provide your response."
109
- end
110
105
  end
111
106
 
112
107
  # Gemini preparation
data/lib/dspy/lm.rb CHANGED
@@ -26,6 +26,7 @@ require_relative 'lm/json_strategy'
26
26
  # Load message builder and message types
27
27
  require_relative 'lm/message'
28
28
  require_relative 'lm/message_builder'
29
+ require_relative 'structured_outputs_prompt'
29
30
 
30
31
  module DSPy
31
32
  class LM
@@ -176,26 +177,53 @@ module DSPy
176
177
 
177
178
  def build_messages(inference_module, input_values)
178
179
  messages = []
179
-
180
+
181
+ # Determine if structured outputs will be used and wrap prompt if so
182
+ base_prompt = inference_module.prompt
183
+ prompt = if will_use_structured_outputs?(inference_module.signature_class)
184
+ StructuredOutputsPrompt.new(**base_prompt.to_h)
185
+ else
186
+ base_prompt
187
+ end
188
+
180
189
  # Add system message
181
- system_prompt = inference_module.system_signature
190
+ system_prompt = prompt.render_system_prompt
182
191
  if system_prompt
183
192
  messages << Message.new(
184
193
  role: Message::Role::System,
185
194
  content: system_prompt
186
195
  )
187
196
  end
188
-
197
+
189
198
  # Add user message
190
- user_prompt = inference_module.user_signature(input_values)
199
+ user_prompt = prompt.render_user_prompt(input_values)
191
200
  messages << Message.new(
192
201
  role: Message::Role::User,
193
202
  content: user_prompt
194
203
  )
195
-
204
+
196
205
  messages
197
206
  end
198
207
 
208
+ def will_use_structured_outputs?(signature_class)
209
+ return false unless signature_class
210
+
211
+ adapter_class_name = adapter.class.name
212
+
213
+ if adapter_class_name.include?('OpenAIAdapter') || adapter_class_name.include?('OllamaAdapter')
214
+ adapter.instance_variable_get(:@structured_outputs_enabled) &&
215
+ DSPy::LM::Adapters::OpenAI::SchemaConverter.supports_structured_outputs?(adapter.model)
216
+ elsif adapter_class_name.include?('GeminiAdapter')
217
+ adapter.instance_variable_get(:@structured_outputs_enabled) &&
218
+ DSPy::LM::Adapters::Gemini::SchemaConverter.supports_structured_outputs?(adapter.model)
219
+ elsif adapter_class_name.include?('AnthropicAdapter')
220
+ structured_outputs_enabled = adapter.instance_variable_get(:@structured_outputs_enabled)
221
+ structured_outputs_enabled.nil? ? true : structured_outputs_enabled
222
+ else
223
+ false
224
+ end
225
+ end
226
+
199
227
  def parse_response(response, input_values, signature_class)
200
228
  # Try to parse the response as JSON
201
229
  content = response.content
@@ -208,26 +208,26 @@ module DSPy
208
208
  sig { params(value: T.untyped, union_type: T.untyped).returns(T.untyped) }
209
209
  def coerce_union_value(value, union_type)
210
210
  return value unless value.is_a?(Hash)
211
-
211
+
212
212
  # Check for _type discriminator field
213
213
  type_name = value[:_type] || value["_type"]
214
214
  return value unless type_name
215
-
215
+
216
216
  # Find matching struct type in the union
217
217
  union_type.types.each do |type|
218
218
  next if type == T::Utils.coerce(NilClass)
219
-
219
+
220
220
  if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
221
221
  struct_name = type.raw_type.name.split('::').last
222
222
  if struct_name == type_name
223
223
  # Convert string keys to symbols and remove _type
224
224
  symbolized_hash = value.transform_keys(&:to_sym)
225
225
  symbolized_hash.delete(:_type)
226
-
226
+
227
227
  # Coerce struct field values based on their types
228
228
  struct_class = type.raw_type
229
229
  struct_props = struct_class.props
230
-
230
+
231
231
  # ONLY include fields that exist in the struct
232
232
  coerced_hash = {}
233
233
  struct_props.each_key do |key|
@@ -236,13 +236,13 @@ module DSPy
236
236
  coerced_hash[key] = coerce_value_to_type(symbolized_hash[key], prop_type)
237
237
  end
238
238
  end
239
-
239
+
240
240
  # Create the struct instance with coerced values
241
241
  return struct_class.new(**coerced_hash)
242
242
  end
243
243
  end
244
244
  end
245
-
245
+
246
246
  # If no matching type found, return original value
247
247
  value
248
248
  rescue ArgumentError => e
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sorbet-runtime'
4
+ require_relative 'prompt'
5
+
6
+ module DSPy
7
+ # Optimized prompt for structured outputs that omits redundant schema information
8
+ # since the schema is already enforced by API parameters (response_format, generation_config, tools)
9
+ class StructuredOutputsPrompt < Prompt
10
+ extend T::Sig
11
+
12
+ # Render minimal system prompt without output schema or JSON formatting instructions
13
+ sig { returns(String) }
14
+ def render_system_prompt
15
+ sections = []
16
+
17
+ sections << "Your input schema fields are:"
18
+ sections << "```json"
19
+ sections << JSON.pretty_generate(@input_schema)
20
+ sections << "```"
21
+
22
+ # Add few-shot examples if present
23
+ if @few_shot_examples.any?
24
+ sections << ""
25
+ sections << "Here are some examples:"
26
+ sections << ""
27
+ @few_shot_examples.each_with_index do |example, index|
28
+ sections << "### Example #{index + 1}"
29
+ sections << example.to_prompt_section
30
+ sections << ""
31
+ end
32
+ end
33
+
34
+ sections << ""
35
+ sections << "Your objective is: #{@instruction}"
36
+
37
+ sections.join("\n")
38
+ end
39
+
40
+ # Render minimal user prompt without JSON formatting instructions
41
+ sig { params(input_values: T::Hash[Symbol, T.untyped]).returns(String) }
42
+ def render_user_prompt(input_values)
43
+ sections = []
44
+
45
+ sections << "## Input Values"
46
+ sections << "```json"
47
+ sections << JSON.pretty_generate(serialize_for_json(input_values))
48
+ sections << "```"
49
+
50
+ sections.join("\n")
51
+ end
52
+ end
53
+ end
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.28.0"
4
+ VERSION = "0.28.1"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.28.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-10-02 00:00:00.000000000 Z
10
+ date: 2025-10-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -247,6 +247,7 @@ files:
247
247
  - lib/dspy/signature.rb
248
248
  - lib/dspy/storage/program_storage.rb
249
249
  - lib/dspy/storage/storage_manager.rb
250
+ - lib/dspy/structured_outputs_prompt.rb
250
251
  - lib/dspy/teleprompt/data_handler.rb
251
252
  - lib/dspy/teleprompt/gepa.rb
252
253
  - lib/dspy/teleprompt/mipro_v2.rb