dspy 0.27.5 → 0.28.0

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.
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_strategy"
4
- require_relative "../adapters/gemini/schema_converter"
5
-
6
- module DSPy
7
- class LM
8
- module Strategies
9
- # Strategy for using Gemini's native structured output feature
10
- class GeminiStructuredOutputStrategy < BaseStrategy
11
- extend T::Sig
12
-
13
- sig { override.returns(T::Boolean) }
14
- def available?
15
- # Check if adapter is Gemini and supports structured outputs
16
- return false unless adapter.is_a?(DSPy::LM::GeminiAdapter)
17
- return false unless adapter.instance_variable_get(:@structured_outputs_enabled)
18
-
19
- DSPy::LM::Adapters::Gemini::SchemaConverter.supports_structured_outputs?(adapter.model)
20
- end
21
-
22
- sig { override.returns(Integer) }
23
- def priority
24
- 100 # Highest priority - native structured outputs are most reliable
25
- end
26
-
27
- sig { override.returns(String) }
28
- def name
29
- "gemini_structured_output"
30
- end
31
-
32
- sig { override.params(messages: T::Array[T::Hash[Symbol, String]], request_params: T::Hash[Symbol, T.untyped]).void }
33
- def prepare_request(messages, request_params)
34
- # Convert signature to Gemini JSON Schema format (supports oneOf/anyOf for unions)
35
- schema = DSPy::LM::Adapters::Gemini::SchemaConverter.to_gemini_format(signature_class)
36
-
37
- # Add generation_config for structured output using JSON Schema format
38
- request_params[:generation_config] = {
39
- response_mime_type: "application/json",
40
- response_json_schema: schema # Use JSON Schema format for proper union support
41
- }
42
- end
43
-
44
- sig { override.params(response: DSPy::LM::Response).returns(T.nilable(String)) }
45
- def extract_json(response)
46
- # With Gemini structured outputs, the response should already be valid JSON
47
- # Just return the content as-is
48
- response.content
49
- end
50
-
51
- sig { override.params(error: StandardError).returns(T::Boolean) }
52
- def handle_error(error)
53
- # Handle Gemini-specific structured output errors
54
- error_msg = error.message.to_s.downcase
55
-
56
- # Check for permanent errors that shouldn't be retried
57
- permanent_error_patterns = [
58
- "schema",
59
- "generation_config",
60
- "response_schema",
61
- "unknown name \"response_mime_type\"",
62
- "unknown name \"response_schema\"",
63
- "invalid json payload",
64
- "no matching sse interaction found", # VCR test configuration issue
65
- "cannot find field"
66
- ]
67
-
68
- if permanent_error_patterns.any? { |pattern| error_msg.include?(pattern) }
69
- # These are permanent errors - no point retrying
70
- DSPy.logger.debug("Gemini structured output failed (permanent error, skipping retries): #{error.message}")
71
- true # Skip retries and try next strategy
72
- else
73
- # Unknown error - let retry logic handle it
74
- false
75
- end
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_strategy"
4
-
5
- module DSPy
6
- class LM
7
- module Strategies
8
- # Strategy for using OpenAI's native structured output feature
9
- class OpenAIStructuredOutputStrategy < BaseStrategy
10
- extend T::Sig
11
-
12
- sig { override.returns(T::Boolean) }
13
- def available?
14
- # Check if adapter is OpenAI or Ollama and supports structured outputs
15
- return false unless adapter.is_a?(DSPy::LM::OpenAIAdapter) || adapter.is_a?(DSPy::LM::OllamaAdapter)
16
- return false unless adapter.instance_variable_get(:@structured_outputs_enabled)
17
-
18
- # For Ollama, we assume it supports basic structured outputs
19
- if adapter.is_a?(DSPy::LM::OllamaAdapter)
20
- return true
21
- end
22
-
23
- DSPy::LM::Adapters::OpenAI::SchemaConverter.supports_structured_outputs?(adapter.model)
24
- end
25
-
26
- sig { override.returns(Integer) }
27
- def priority
28
- 100 # Highest priority - native structured outputs are most reliable
29
- end
30
-
31
- sig { override.returns(String) }
32
- def name
33
- "openai_structured_output"
34
- end
35
-
36
- sig { override.params(messages: T::Array[T::Hash[Symbol, String]], request_params: T::Hash[Symbol, T.untyped]).void }
37
- def prepare_request(messages, request_params)
38
- # Add structured output format to request
39
- response_format = DSPy::LM::Adapters::OpenAI::SchemaConverter.to_openai_format(signature_class)
40
- request_params[:response_format] = response_format
41
- end
42
-
43
- sig { override.params(response: DSPy::LM::Response).returns(T.nilable(String)) }
44
- def extract_json(response)
45
- # With structured outputs, the response should already be valid JSON
46
- # Just return the content as-is
47
- response.content
48
- end
49
-
50
- sig { override.params(error: StandardError).returns(T::Boolean) }
51
- def handle_error(error)
52
- # Handle OpenAI-specific structured output errors
53
- if error.message.include?("response_format") || error.message.include?("Invalid schema")
54
- # Log the error and return true to indicate we handled it
55
- # This allows fallback to another strategy
56
- DSPy.logger.warn("OpenAI structured output failed: #{error.message}")
57
- true
58
- else
59
- false
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,144 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sorbet-runtime"
4
- require_relative "strategies/base_strategy"
5
- require_relative "strategies/openai_structured_output_strategy"
6
- require_relative "strategies/anthropic_tool_use_strategy"
7
- require_relative "strategies/anthropic_extraction_strategy"
8
- require_relative "strategies/gemini_structured_output_strategy"
9
- require_relative "strategies/enhanced_prompting_strategy"
10
-
11
- module DSPy
12
- class LM
13
- # Selects the best JSON extraction strategy based on the adapter and capabilities
14
- class StrategySelector
15
- extend T::Sig
16
-
17
- # Strategy names enum for type safety
18
- class StrategyName < T::Enum
19
- enums do
20
- OpenAIStructuredOutput = new('openai_structured_output')
21
- AnthropicToolUse = new('anthropic_tool_use')
22
- AnthropicExtraction = new('anthropic_extraction')
23
- GeminiStructuredOutput = new('gemini_structured_output')
24
- EnhancedPrompting = new('enhanced_prompting')
25
- end
26
- end
27
-
28
- # Available strategies in order of registration
29
- STRATEGIES = [
30
- Strategies::OpenAIStructuredOutputStrategy,
31
- Strategies::AnthropicToolUseStrategy,
32
- Strategies::AnthropicExtractionStrategy,
33
- Strategies::GeminiStructuredOutputStrategy,
34
- Strategies::EnhancedPromptingStrategy
35
- ].freeze
36
-
37
- sig { params(adapter: DSPy::LM::Adapter, signature_class: T.class_of(DSPy::Signature)).void }
38
- def initialize(adapter, signature_class)
39
- @adapter = adapter
40
- @signature_class = signature_class
41
- @strategies = build_strategies
42
- end
43
-
44
- # Select the best available strategy
45
- sig { returns(Strategies::BaseStrategy) }
46
- def select
47
- # Allow manual override via configuration
48
- if DSPy.config.structured_outputs.strategy
49
- strategy = select_strategy_from_preference(DSPy.config.structured_outputs.strategy)
50
- return strategy if strategy&.available?
51
-
52
- # If strict strategy not available, fall back to compatible for Strict preference
53
- if is_strict_preference?(DSPy.config.structured_outputs.strategy)
54
- compatible_strategy = find_strategy_by_name(StrategyName::EnhancedPrompting)
55
- return compatible_strategy if compatible_strategy&.available?
56
- end
57
-
58
- DSPy.logger.warn("No available strategy found for preference '#{DSPy.config.structured_outputs.strategy}'")
59
- end
60
-
61
- # Select the highest priority available strategy
62
- available_strategies = @strategies.select(&:available?)
63
-
64
- if available_strategies.empty?
65
- raise "No JSON extraction strategies available for #{@adapter.class}"
66
- end
67
-
68
- selected = available_strategies.max_by(&:priority)
69
-
70
- DSPy.logger.debug("Selected JSON extraction strategy: #{selected.name}")
71
- selected
72
- end
73
-
74
- # Get all available strategies
75
- sig { returns(T::Array[Strategies::BaseStrategy]) }
76
- def available_strategies
77
- @strategies.select(&:available?)
78
- end
79
-
80
- # Check if a specific strategy is available
81
- sig { params(strategy_name: StrategyName).returns(T::Boolean) }
82
- def strategy_available?(strategy_name)
83
- strategy = find_strategy_by_name(strategy_name)
84
- strategy&.available? || false
85
- end
86
-
87
- private
88
-
89
- # Select internal strategy based on user preference
90
- sig { params(preference: DSPy::Strategy).returns(T.nilable(Strategies::BaseStrategy)) }
91
- def select_strategy_from_preference(preference)
92
- case preference
93
- when DSPy::Strategy::Strict
94
- # Try provider-optimized strategies first
95
- select_provider_optimized_strategy
96
- when DSPy::Strategy::Compatible
97
- # Use enhanced prompting
98
- find_strategy_by_name(StrategyName::EnhancedPrompting)
99
- else
100
- nil
101
- end
102
- end
103
-
104
- # Check if preference is for strict (provider-optimized) strategies
105
- sig { params(preference: DSPy::Strategy).returns(T::Boolean) }
106
- def is_strict_preference?(preference)
107
- preference == DSPy::Strategy::Strict
108
- end
109
-
110
- # Select the best provider-optimized strategy for the current adapter
111
- sig { returns(T.nilable(Strategies::BaseStrategy)) }
112
- def select_provider_optimized_strategy
113
- # Try OpenAI structured output first
114
- openai_strategy = find_strategy_by_name(StrategyName::OpenAIStructuredOutput)
115
- return openai_strategy if openai_strategy&.available?
116
-
117
- # Try Gemini structured output
118
- gemini_strategy = find_strategy_by_name(StrategyName::GeminiStructuredOutput)
119
- return gemini_strategy if gemini_strategy&.available?
120
-
121
- # Try Anthropic tool use first
122
- anthropic_tool_strategy = find_strategy_by_name(StrategyName::AnthropicToolUse)
123
- return anthropic_tool_strategy if anthropic_tool_strategy&.available?
124
-
125
- # Fall back to Anthropic extraction
126
- anthropic_strategy = find_strategy_by_name(StrategyName::AnthropicExtraction)
127
- return anthropic_strategy if anthropic_strategy&.available?
128
-
129
- # No provider-specific strategy available
130
- nil
131
- end
132
-
133
- sig { returns(T::Array[Strategies::BaseStrategy]) }
134
- def build_strategies
135
- STRATEGIES.map { |klass| klass.new(@adapter, @signature_class) }
136
- end
137
-
138
- sig { params(name: StrategyName).returns(T.nilable(Strategies::BaseStrategy)) }
139
- def find_strategy_by_name(name)
140
- @strategies.find { |s| s.name == name.serialize }
141
- end
142
- end
143
- end
144
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sorbet-runtime"
4
-
5
- module DSPy
6
- class LM
7
- # Enum for structured output strategies
8
- class StructuredOutputStrategy < T::Enum
9
- enums do
10
- OpenAIStructuredOutput = new("openai_structured_output")
11
- AnthropicToolUse = new("anthropic_tool_use")
12
- AnthropicExtraction = new("anthropic_extraction")
13
- EnhancedPrompting = new("enhanced_prompting")
14
- end
15
- end
16
- end
17
- end
data/lib/dspy/strategy.rb DELETED
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sorbet-runtime"
4
-
5
- module DSPy
6
- # User-facing enum for structured output strategy preferences
7
- class Strategy < T::Enum
8
- enums do
9
- # Use provider-optimized strategies when available (OpenAI structured outputs, Anthropic extraction)
10
- # Falls back to Compatible if provider-specific strategy isn't available
11
- Strict = new("strict")
12
-
13
- # Use enhanced prompting that works with any provider
14
- # More compatible but potentially less reliable than provider-specific strategies
15
- Compatible = new("compatible")
16
- end
17
- end
18
- end