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 +4 -4
- data/lib/dspy/lm/adapter_factory.rb +6 -3
- data/lib/dspy/lm/adapters/gemini/schema_converter.rb +20 -15
- data/lib/dspy/lm/adapters/gemini_adapter.rb +7 -2
- data/lib/dspy/lm/adapters/openai_adapter.rb +11 -3
- data/lib/dspy/lm/adapters/openrouter_adapter.rb +68 -0
- data/lib/dspy/lm.rb +1 -0
- data/lib/dspy/observability/async_span_processor.rb +2 -0
- data/lib/dspy/observability.rb +17 -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: dca273acbd2ba8ab0ab9e2980bdf8048cc9ea80b3ce1b74d6f1e911e165ae895
|
4
|
+
data.tar.gz: '08177e7841275140c7f074ac3a700883f06c6610c9bfd4795d142a3c838c0b24'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
28
|
-
adapter_options.merge!(options) if
|
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
|
14
|
+
# Based on official Google documentation (Sept 2025)
|
15
15
|
STRUCTURED_OUTPUT_MODELS = T.let([
|
16
|
-
|
17
|
-
"gemini-1.5-pro
|
18
|
-
"gemini-1.5-pro-preview-
|
19
|
-
"gemini-
|
20
|
-
"gemini-
|
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
|
24
|
-
|
25
|
-
|
26
|
-
"gemini-
|
27
|
-
"gemini-1.
|
28
|
-
"gemini-1.0-pro
|
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
|
-
|
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
|
data/lib/dspy/observability.rb
CHANGED
@@ -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
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.
|
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-
|
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
|