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 +4 -4
- data/lib/dspy/lm/adapters/gemini/schema_converter.rb +25 -16
- data/lib/dspy/lm/json_strategy.rb +0 -5
- data/lib/dspy/lm.rb +33 -5
- data/lib/dspy/mixins/type_coercion.rb +7 -7
- data/lib/dspy/structured_outputs_prompt.rb +53 -0
- data/lib/dspy/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b377060443eeb9c3c5d975e76750d6c519d1b93cf6f20dc6ad20bcda08d1ca4
|
4
|
+
data.tar.gz: 4580845b3fd9991b531c8c2bd809595cbd3328da57a23e58307cdbe52e3822bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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-
|
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
|
28
|
+
# Models that do not support structured outputs or are deprecated
|
32
29
|
UNSUPPORTED_MODELS = T.let([
|
33
|
-
# Legacy Gemini 1.0 series
|
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
|
-
|
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 =
|
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 =
|
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
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.
|
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-
|
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
|