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.
- checksums.yaml +4 -4
- data/README.md +28 -9
- data/lib/dspy/lm/adapter_factory.rb +1 -1
- data/lib/dspy/lm/adapters/anthropic_adapter.rb +3 -2
- data/lib/dspy/lm/chat_strategy.rb +38 -0
- data/lib/dspy/lm/json_strategy.rb +222 -0
- data/lib/dspy/lm.rb +13 -16
- data/lib/dspy/re_act.rb +253 -68
- data/lib/dspy/signature.rb +2 -251
- data/lib/dspy/tools/base.rb +5 -7
- data/lib/dspy/type_system/sorbet_json_schema.rb +56 -18
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +0 -8
- metadata +4 -12
- data/lib/dspy/lm/retry_handler.rb +0 -132
- data/lib/dspy/lm/strategies/anthropic_extraction_strategy.rb +0 -78
- data/lib/dspy/lm/strategies/anthropic_tool_use_strategy.rb +0 -192
- data/lib/dspy/lm/strategies/base_strategy.rb +0 -53
- data/lib/dspy/lm/strategies/enhanced_prompting_strategy.rb +0 -178
- data/lib/dspy/lm/strategies/gemini_structured_output_strategy.rb +0 -80
- data/lib/dspy/lm/strategies/openai_structured_output_strategy.rb +0 -65
- data/lib/dspy/lm/strategy_selector.rb +0 -144
- data/lib/dspy/lm/structured_output_strategy.rb +0 -17
- data/lib/dspy/strategy.rb +0 -18
@@ -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
|