dspy 0.12.0 → 0.13.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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d80d5b0166fe5a101e4918ffee13a70dec6ca67b493cf2e68dff1c18b2df36c1
|
4
|
+
data.tar.gz: 1687fe88d41c5d4627592ff5e98f87ca9f40186870386291b1ead091f51235da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bedebf2e58243bedcf8003d25b4f55789a7e4a611f9f1997a322e93db553fe117fde155415c5a607a65b49f61f2a640c7899a0fd4793bcdf9e8672602f54755
|
7
|
+
data.tar.gz: cdf39e605a7550e94334c5bad13ccc5af2a255b7039559b56b918e1ba3706e671c52960558459a0c96edab318c68bc9828b1eb03530b47daa896766a2cb7aa7d
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../lm/usage'
|
4
|
+
|
3
5
|
module DSPy
|
4
6
|
module Instrumentation
|
5
7
|
# Utility for extracting token usage from different LM adapters
|
@@ -9,6 +11,18 @@ module DSPy
|
|
9
11
|
|
10
12
|
# Extract actual token usage from API responses
|
11
13
|
def extract_token_usage(response, provider)
|
14
|
+
return {} unless response&.usage
|
15
|
+
|
16
|
+
# Handle Usage struct
|
17
|
+
if response.usage.is_a?(DSPy::LM::Usage) || response.usage.is_a?(DSPy::LM::OpenAIUsage)
|
18
|
+
return {
|
19
|
+
input_tokens: response.usage.input_tokens,
|
20
|
+
output_tokens: response.usage.output_tokens,
|
21
|
+
total_tokens: response.usage.total_tokens
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Fallback to legacy hash handling
|
12
26
|
case provider.to_s.downcase
|
13
27
|
when 'openai'
|
14
28
|
extract_openai_tokens(response)
|
@@ -27,11 +41,12 @@ module DSPy
|
|
27
41
|
usage = response.usage
|
28
42
|
return {} unless usage.is_a?(Hash)
|
29
43
|
|
44
|
+
# Handle both symbol and string keys for VCR compatibility
|
30
45
|
{
|
31
46
|
input_tokens: usage[:prompt_tokens] || usage['prompt_tokens'],
|
32
47
|
output_tokens: usage[:completion_tokens] || usage['completion_tokens'],
|
33
48
|
total_tokens: usage[:total_tokens] || usage['total_tokens']
|
34
|
-
}
|
49
|
+
}.compact # Remove nil values
|
35
50
|
end
|
36
51
|
|
37
52
|
def extract_anthropic_tokens(response)
|
@@ -40,6 +55,7 @@ module DSPy
|
|
40
55
|
usage = response.usage
|
41
56
|
return {} unless usage.is_a?(Hash)
|
42
57
|
|
58
|
+
# Handle both symbol and string keys for VCR compatibility
|
43
59
|
input_tokens = usage[:input_tokens] || usage['input_tokens'] || 0
|
44
60
|
output_tokens = usage[:output_tokens] || usage['output_tokens'] || 0
|
45
61
|
|
@@ -47,7 +63,7 @@ module DSPy
|
|
47
63
|
input_tokens: input_tokens,
|
48
64
|
output_tokens: output_tokens,
|
49
65
|
total_tokens: input_tokens + output_tokens
|
50
|
-
}
|
66
|
+
}.compact # Remove nil values
|
51
67
|
end
|
52
68
|
end
|
53
69
|
end
|
@@ -63,9 +63,12 @@ module DSPy
|
|
63
63
|
content = response.content.first.text if response.content.is_a?(Array) && response.content.first
|
64
64
|
usage = response.usage
|
65
65
|
|
66
|
+
# Convert usage data to typed struct
|
67
|
+
usage_struct = UsageFactory.create('anthropic', usage)
|
68
|
+
|
66
69
|
Response.new(
|
67
70
|
content: content,
|
68
|
-
usage:
|
71
|
+
usage: usage_struct,
|
69
72
|
metadata: {
|
70
73
|
provider: 'anthropic',
|
71
74
|
model: model,
|
@@ -52,9 +52,12 @@ module DSPy
|
|
52
52
|
raise AdapterError, "OpenAI refused to generate output: #{message.refusal}"
|
53
53
|
end
|
54
54
|
|
55
|
+
# Convert usage data to typed struct
|
56
|
+
usage_struct = UsageFactory.create('openai', usage)
|
57
|
+
|
55
58
|
Response.new(
|
56
59
|
content: content,
|
57
|
-
usage:
|
60
|
+
usage: usage_struct,
|
58
61
|
metadata: {
|
59
62
|
provider: 'openai',
|
60
63
|
model: model,
|
data/lib/dspy/lm/response.rb
CHANGED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
module DSPy
|
6
|
+
class LM
|
7
|
+
# Base class for token usage information
|
8
|
+
class Usage < T::Struct
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
const :input_tokens, Integer
|
12
|
+
const :output_tokens, Integer
|
13
|
+
const :total_tokens, Integer
|
14
|
+
|
15
|
+
sig { returns(Hash) }
|
16
|
+
def to_h
|
17
|
+
{
|
18
|
+
input_tokens: input_tokens,
|
19
|
+
output_tokens: output_tokens,
|
20
|
+
total_tokens: total_tokens
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# OpenAI-specific usage information with additional fields
|
26
|
+
class OpenAIUsage < T::Struct
|
27
|
+
extend T::Sig
|
28
|
+
|
29
|
+
const :input_tokens, Integer
|
30
|
+
const :output_tokens, Integer
|
31
|
+
const :total_tokens, Integer
|
32
|
+
const :prompt_tokens_details, T.nilable(T::Hash[Symbol, Integer]), default: nil
|
33
|
+
const :completion_tokens_details, T.nilable(T::Hash[Symbol, Integer]), default: nil
|
34
|
+
|
35
|
+
sig { returns(Hash) }
|
36
|
+
def to_h
|
37
|
+
base = {
|
38
|
+
input_tokens: input_tokens,
|
39
|
+
output_tokens: output_tokens,
|
40
|
+
total_tokens: total_tokens
|
41
|
+
}
|
42
|
+
base[:prompt_tokens_details] = prompt_tokens_details if prompt_tokens_details
|
43
|
+
base[:completion_tokens_details] = completion_tokens_details if completion_tokens_details
|
44
|
+
base
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Factory for creating appropriate usage objects
|
49
|
+
module UsageFactory
|
50
|
+
extend T::Sig
|
51
|
+
|
52
|
+
sig { params(provider: String, usage_data: T.untyped).returns(T.nilable(T.any(Usage, OpenAIUsage))) }
|
53
|
+
def self.create(provider, usage_data)
|
54
|
+
return nil if usage_data.nil?
|
55
|
+
|
56
|
+
# If already a Usage struct, return as-is
|
57
|
+
return usage_data if usage_data.is_a?(Usage)
|
58
|
+
|
59
|
+
# Handle test doubles by converting to hash
|
60
|
+
if usage_data.respond_to?(:to_h)
|
61
|
+
usage_data = usage_data.to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert hash to appropriate struct
|
65
|
+
return nil unless usage_data.is_a?(Hash)
|
66
|
+
|
67
|
+
# Normalize keys to symbols
|
68
|
+
normalized = usage_data.transform_keys(&:to_sym)
|
69
|
+
|
70
|
+
case provider.to_s.downcase
|
71
|
+
when 'openai'
|
72
|
+
create_openai_usage(normalized)
|
73
|
+
when 'anthropic'
|
74
|
+
create_anthropic_usage(normalized)
|
75
|
+
else
|
76
|
+
create_generic_usage(normalized)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
sig { params(data: T::Hash[Symbol, T.untyped]).returns(T.nilable(OpenAIUsage)) }
|
83
|
+
def self.create_openai_usage(data)
|
84
|
+
# OpenAI uses prompt_tokens/completion_tokens
|
85
|
+
input_tokens = data[:prompt_tokens] || data[:input_tokens] || 0
|
86
|
+
output_tokens = data[:completion_tokens] || data[:output_tokens] || 0
|
87
|
+
total_tokens = data[:total_tokens] || (input_tokens + output_tokens)
|
88
|
+
|
89
|
+
# Convert prompt_tokens_details and completion_tokens_details to hashes if needed
|
90
|
+
prompt_details = convert_to_hash(data[:prompt_tokens_details])
|
91
|
+
completion_details = convert_to_hash(data[:completion_tokens_details])
|
92
|
+
|
93
|
+
OpenAIUsage.new(
|
94
|
+
input_tokens: input_tokens,
|
95
|
+
output_tokens: output_tokens,
|
96
|
+
total_tokens: total_tokens,
|
97
|
+
prompt_tokens_details: prompt_details,
|
98
|
+
completion_tokens_details: completion_details
|
99
|
+
)
|
100
|
+
rescue => e
|
101
|
+
DSPy.logger.debug("Failed to create OpenAI usage: #{e.message}")
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
sig { params(value: T.untyped).returns(T.nilable(T::Hash[Symbol, Integer])) }
|
106
|
+
def self.convert_to_hash(value)
|
107
|
+
return nil if value.nil?
|
108
|
+
return value if value.is_a?(Hash) && value.keys.all? { |k| k.is_a?(Symbol) }
|
109
|
+
|
110
|
+
# Convert object to hash if it responds to to_h
|
111
|
+
if value.respond_to?(:to_h)
|
112
|
+
hash = value.to_h
|
113
|
+
# Ensure symbol keys and integer values
|
114
|
+
hash.transform_keys(&:to_sym).transform_values(&:to_i)
|
115
|
+
else
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(data: T::Hash[Symbol, T.untyped]).returns(T.nilable(Usage)) }
|
123
|
+
def self.create_anthropic_usage(data)
|
124
|
+
# Anthropic uses input_tokens/output_tokens
|
125
|
+
input_tokens = data[:input_tokens] || 0
|
126
|
+
output_tokens = data[:output_tokens] || 0
|
127
|
+
total_tokens = data[:total_tokens] || (input_tokens + output_tokens)
|
128
|
+
|
129
|
+
Usage.new(
|
130
|
+
input_tokens: input_tokens,
|
131
|
+
output_tokens: output_tokens,
|
132
|
+
total_tokens: total_tokens
|
133
|
+
)
|
134
|
+
rescue => e
|
135
|
+
DSPy.logger.debug("Failed to create Anthropic usage: #{e.message}")
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
sig { params(data: T::Hash[Symbol, T.untyped]).returns(T.nilable(Usage)) }
|
140
|
+
def self.create_generic_usage(data)
|
141
|
+
# Generic fallback
|
142
|
+
input_tokens = data[:input_tokens] || data[:prompt_tokens] || 0
|
143
|
+
output_tokens = data[:output_tokens] || data[:completion_tokens] || 0
|
144
|
+
total_tokens = data[:total_tokens] || (input_tokens + output_tokens)
|
145
|
+
|
146
|
+
Usage.new(
|
147
|
+
input_tokens: input_tokens,
|
148
|
+
output_tokens: output_tokens,
|
149
|
+
total_tokens: total_tokens
|
150
|
+
)
|
151
|
+
rescue => e
|
152
|
+
DSPy.logger.debug("Failed to create generic usage: #{e.message}")
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/dspy/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dspy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vicente Reig Rincón de Arellano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-configurable
|
@@ -186,6 +186,7 @@ files:
|
|
186
186
|
- lib/dspy/lm/strategies/openai_structured_output_strategy.rb
|
187
187
|
- lib/dspy/lm/strategy_selector.rb
|
188
188
|
- lib/dspy/lm/structured_output_strategy.rb
|
189
|
+
- lib/dspy/lm/usage.rb
|
189
190
|
- lib/dspy/memory.rb
|
190
191
|
- lib/dspy/memory/embedding_engine.rb
|
191
192
|
- lib/dspy/memory/in_memory_store.rb
|