dspy 0.27.3 → 0.27.4

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: d697eb8eb574ca5c23914c1911f1d7a03ad7411aa83b19bedf2231cacc544460
4
- data.tar.gz: 3086cbaa86d01b0dd09512c9f5893f8a31b8d9988eed6782a967c24e1c12fb01
3
+ metadata.gz: dca273acbd2ba8ab0ab9e2980bdf8048cc9ea80b3ce1b74d6f1e911e165ae895
4
+ data.tar.gz: '08177e7841275140c7f074ac3a700883f06c6610c9bfd4795d142a3c838c0b24'
5
5
  SHA512:
6
- metadata.gz: eae9e4cba177e6cea359f1ffd55ebaa4203cc5a6594b86ab5fc2b9b9e8c54cf24838a8d18102ab96fc9a8f4b85827c3cb02ef0b75c3d077c6c70301abb52f48d
7
- data.tar.gz: ecc26be5f85df66e911a71d5a1fa878cc42c79c7d63e42b2d1440836859814837f4ba782db18584161598867a5aef5ebbfef056a2988b4208767b8e0c1999013
6
+ metadata.gz: 1b8d4bd756ab303688366fb0bcb5e5a3e045152c5695a8cdcd5291ca6313fc8ee7bd8051c6026663cfda0ec429b7977aa2355442595ba803bfedafd3926e89b6
7
+ data.tar.gz: 30efa43a8bc34708003a178d7715ae6688268b4b63e98789f011e47f7d36b8c30f292ac986c8df3767bafa517b7b73f5ff7fa92fec8013732c69952dfa79df71
@@ -9,9 +9,12 @@ module DSPy
9
9
  'openai' => 'OpenAIAdapter',
10
10
  'anthropic' => 'AnthropicAdapter',
11
11
  'ollama' => 'OllamaAdapter',
12
- 'gemini' => 'GeminiAdapter'
12
+ 'gemini' => 'GeminiAdapter',
13
+ 'openrouter' => 'OpenrouterAdapter'
13
14
  }.freeze
14
15
 
16
+ PROVIDERS_WITH_EXTRA_OPTIONS = %w[openai ollama gemini openrouter].freeze
17
+
15
18
  class << self
16
19
  # Creates an adapter instance based on model_id
17
20
  # @param model_id [String] Full model identifier (e.g., "openai/gpt-4")
@@ -24,8 +27,8 @@ module DSPy
24
27
 
25
28
  # Pass provider-specific options
26
29
  adapter_options = { model: model, api_key: api_key }
27
- # OpenAI, Ollama, and Gemini accept additional options
28
- adapter_options.merge!(options) if %w[openai ollama gemini].include?(provider)
30
+ # Some providers accept additional options
31
+ adapter_options.merge!(options) if PROVIDERS_WITH_EXTRA_OPTIONS.include?(provider)
29
32
 
30
33
  adapter_class.new(**adapter_options)
31
34
  end
@@ -11,24 +11,29 @@ module DSPy
11
11
  extend T::Sig
12
12
 
13
13
  # Models that support structured outputs (JSON + Schema)
14
- # Based on official Google documentation and gemini-ai gem table
14
+ # Based on official Google documentation (Sept 2025)
15
15
  STRUCTURED_OUTPUT_MODELS = T.let([
16
- "gemini-1.5-pro", # ✅ Full schema support (legacy)
17
- "gemini-1.5-pro-preview-0514", # ✅ Full schema support (legacy)
18
- "gemini-1.5-pro-preview-0409", # ✅ Full schema support (legacy)
19
- "gemini-2.5-flash", # ✅ Full schema support (2025 current)
20
- "gemini-2.5-flash-lite" # ✅ Full schema support (2025 current)
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
+ # Gemini 2.0 series
23
+ "gemini-2.0-flash",
24
+ "gemini-2.0-flash-001",
25
+ # Gemini 2.5 series
26
+ "gemini-2.5-pro",
27
+ "gemini-2.5-flash",
28
+ "gemini-2.5-flash-lite"
21
29
  ].freeze, T::Array[String])
22
30
 
23
- # Models that support JSON mode but NOT schema
24
- JSON_ONLY_MODELS = T.let([
25
- "gemini-pro", # 🟡 JSON only, no schema
26
- "gemini-1.5-flash", # 🟡 JSON only, no schema (legacy)
27
- "gemini-1.5-flash-preview-0514", # 🟡 JSON only, no schema (legacy)
28
- "gemini-1.0-pro-002", # 🟡 JSON only, no schema
29
- "gemini-1.0-pro", # 🟡 JSON only, no schema
30
- "gemini-2.0-flash-001", # 🟡 JSON only, no schema (2025)
31
- "gemini-2.0-flash-lite-001" # 🟡 JSON only, no schema (2025)
31
+ # Models that do not support structured outputs (legacy only)
32
+ UNSUPPORTED_MODELS = T.let([
33
+ # Legacy Gemini 1.0 series only
34
+ "gemini-pro",
35
+ "gemini-1.0-pro-002",
36
+ "gemini-1.0-pro"
32
37
  ].freeze, T::Array[String])
33
38
 
34
39
  sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T::Hash[Symbol, T.untyped]) }
@@ -14,11 +14,16 @@ module DSPy
14
14
  @structured_outputs_enabled = structured_outputs
15
15
 
16
16
  # Disable streaming for VCR tests since SSE responses don't record properly
17
+ # But keep streaming enabled for SSEVCR tests (SSE-specific cassettes)
17
18
  @use_streaming = true
18
19
  begin
19
- @use_streaming = false if defined?(VCR) && VCR.current_cassette
20
+ vcr_active = defined?(VCR) && VCR.current_cassette
21
+ ssevcr_active = defined?(SSEVCR) && SSEVCR.turned_on?
22
+
23
+ # Only disable streaming if regular VCR is active but SSEVCR is not
24
+ @use_streaming = false if vcr_active && !ssevcr_active
20
25
  rescue
21
- # If VCR is not available or any error occurs, use streaming
26
+ # If VCR/SSEVCR is not available or any error occurs, use streaming
22
27
  @use_streaming = true
23
28
  end
24
29
 
@@ -29,10 +29,9 @@ module DSPy
29
29
  normalized_messages = handle_o1_messages(normalized_messages)
30
30
  end
31
31
 
32
- request_params = {
33
- model: model,
32
+ request_params = default_request_params.merge(
34
33
  messages: normalized_messages
35
- }
34
+ )
36
35
 
37
36
  # Add temperature based on model capabilities
38
37
  unless o1_model?(model)
@@ -123,6 +122,15 @@ module DSPy
123
122
  end
124
123
  end
125
124
 
125
+ protected
126
+
127
+ # Allow subclasses to override request params (add headers, etc)
128
+ def default_request_params
129
+ {
130
+ model: model
131
+ }
132
+ end
133
+
126
134
  private
127
135
 
128
136
  def supports_structured_outputs?
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module DSPy
6
+ class LM
7
+ class OpenrouterAdapter < OpenAIAdapter
8
+ BASE_URL = 'https://openrouter.ai/api/v1'
9
+
10
+ def initialize(model:, api_key: nil, structured_outputs: true, http_referrer: nil, x_title: nil)
11
+ # Don't call parent's initialize, do it manually to control client creation
12
+ @model = model
13
+ @api_key = api_key
14
+ @structured_outputs_enabled = structured_outputs
15
+
16
+ @http_referrer = http_referrer
17
+ @x_title = x_title
18
+
19
+ validate_configuration!
20
+
21
+ # Create client with custom base URL
22
+ @client = OpenAI::Client.new(
23
+ api_key: @api_key,
24
+ base_url: BASE_URL
25
+ )
26
+ end
27
+
28
+ def chat(messages:, signature: nil, response_format: nil, &block)
29
+ # For OpenRouter, we need to be more lenient with structured outputs
30
+ # as the model behind it may not fully support OpenAI's response_format spec
31
+ begin
32
+ super
33
+ rescue => e
34
+ # If structured output fails, retry with enhanced prompting
35
+ if @structured_outputs_enabled && signature && e.message.include?('response_format')
36
+ DSPy.logger.debug("OpenRouter structured output failed, falling back to enhanced prompting")
37
+ @structured_outputs_enabled = false
38
+ retry
39
+ else
40
+ raise
41
+ end
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ # Add any OpenRouter-specific headers to all requests
48
+ def default_request_params
49
+ headers = {
50
+ 'X-Title' => @x_title,
51
+ 'HTTP-Referer' => @http_referrer
52
+ }.compact
53
+
54
+ upstream_params = super
55
+ upstream_params.merge!(request_options: { extra_headers: headers }) if headers.any?
56
+ upstream_params
57
+ end
58
+
59
+ private
60
+
61
+ def supports_structured_outputs?
62
+ # Different models behind OpenRouter may have different capabilities
63
+ # For now, we rely on whatever was passed to the constructor
64
+ @structured_outputs_enabled
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/dspy/lm.rb CHANGED
@@ -17,6 +17,7 @@ require_relative 'lm/adapters/openai_adapter'
17
17
  require_relative 'lm/adapters/anthropic_adapter'
18
18
  require_relative 'lm/adapters/ollama_adapter'
19
19
  require_relative 'lm/adapters/gemini_adapter'
20
+ require_relative 'lm/adapters/openrouter_adapter'
20
21
 
21
22
  # Load strategy system
22
23
  require_relative 'lm/strategy_selector'
@@ -107,6 +107,7 @@ module DSPy
107
107
 
108
108
  def start_export_task
109
109
  return if @export_interval <= 0 # Disable timer for testing
110
+ return if ENV['DSPY_DISABLE_OBSERVABILITY'] == 'true' # Skip in tests
110
111
 
111
112
  # Start timer-based export task in background
112
113
  Thread.new do
@@ -129,6 +130,7 @@ module DSPy
129
130
 
130
131
  def trigger_export_if_batch_full
131
132
  return if @queue.size < @export_batch_size
133
+ return if ENV['DSPY_DISABLE_OBSERVABILITY'] == 'true' # Skip in tests
132
134
 
133
135
  # Trigger immediate export in background
134
136
  Thread.new do
@@ -11,6 +11,12 @@ module DSPy
11
11
  def configure!
12
12
  @enabled = false
13
13
 
14
+ # Check for explicit disable flag first
15
+ if ENV['DSPY_DISABLE_OBSERVABILITY'] == 'true'
16
+ DSPy.log('observability.disabled', reason: 'Explicitly disabled via DSPY_DISABLE_OBSERVABILITY')
17
+ return
18
+ end
19
+
14
20
  # Check for required Langfuse environment variables
15
21
  public_key = ENV['LANGFUSE_PUBLIC_KEY']
16
22
  secret_key = ENV['LANGFUSE_SECRET_KEY']
@@ -130,6 +136,17 @@ module DSPy
130
136
 
131
137
  def reset!
132
138
  @enabled = false
139
+
140
+ # Shutdown OpenTelemetry if it's configured
141
+ if defined?(OpenTelemetry) && OpenTelemetry.tracer_provider
142
+ begin
143
+ OpenTelemetry.tracer_provider.shutdown(timeout: 1.0)
144
+ rescue => e
145
+ # Ignore shutdown errors in tests - log them but don't fail
146
+ DSPy.log('observability.shutdown_error', error: e.message) if respond_to?(:log)
147
+ end
148
+ end
149
+
133
150
  @tracer = nil
134
151
  @endpoint = nil
135
152
  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.27.3"
4
+ VERSION = "0.27.4"
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.27.3
4
+ version: 0.27.4
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-09-20 00:00:00.000000000 Z
10
+ date: 2025-09-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -212,6 +212,7 @@ files:
212
212
  - lib/dspy/lm/adapters/ollama_adapter.rb
213
213
  - lib/dspy/lm/adapters/openai/schema_converter.rb
214
214
  - lib/dspy/lm/adapters/openai_adapter.rb
215
+ - lib/dspy/lm/adapters/openrouter_adapter.rb
215
216
  - lib/dspy/lm/errors.rb
216
217
  - lib/dspy/lm/message.rb
217
218
  - lib/dspy/lm/message_builder.rb