riffer 0.38.1 → 0.39.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: 81a97541ff82791dbf24d5bcad216678db7db1262d3f0e18e4dfe18d77169b20
4
- data.tar.gz: 294d8493c2e6349e6005c190deb15c7b56c1d1cfc49bcdafa2d99ea39fb4b1e3
3
+ metadata.gz: b1bf92e3bef065a4c07aadb273a7f6770b711c0f15f268726133c841cb3fd510
4
+ data.tar.gz: d7361c029b98ca819932e2c3d2d53113710bb434ad60092ee0578179fa844c5e
5
5
  SHA512:
6
- metadata.gz: 7004eefa26eaa6bd7a02ff31ae04a4596c063f591b0375f7089e8f39037254945b32a5e57ef6595ddc3f0845173b07d5e020b01c553b573df2a9b535f21d15a2
7
- data.tar.gz: c0444136536b7034241212e9bca19ad634c98080a1491af4cb32e51a2dcecf0566dcc0e5cd798e2fd433a0bebf1aff02fee46f231454500afaa1a8fb7e5675a3
6
+ metadata.gz: 7f503377743f6629a1423234afb158570dc33ee225c82c0bc46e28938a839fe25b5bdfad6f61da274a8bf81e1e8dd6c505ad826593c5eb269251d47667fe9d7c
7
+ data.tar.gz: eddabf85e5416c777c3a9a441ce99cd1cb902ec1c339fa7338890ae8cb7e78c5e78628e1b886aea44cdc6228b99299fa3983816e10075d8c28b59229e4c0b376
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.38.1"
2
+ ".": "0.39.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.39.0](https://github.com/janeapp/riffer/compare/riffer/v0.38.1...riffer/v0.39.0) (2026-07-29)
9
+
10
+
11
+ ### Features
12
+
13
+ * **evals:** expose token usage on evaluation results ([#380](https://github.com/janeapp/riffer/issues/380)) ([32d576e](https://github.com/janeapp/riffer/commit/32d576e492809845c36f44ebf37de3af3b6785b0))
14
+
8
15
  ## [0.38.1](https://github.com/janeapp/riffer/compare/riffer/v0.38.0...riffer/v0.38.1) (2026-07-29)
9
16
 
10
17
 
data/docs/11_EVALS.md CHANGED
@@ -108,9 +108,11 @@ Per-scenario `context` overrides the top-level value. Scenarios without their ow
108
108
  The runner returns a `Riffer::Evals::RunResult`:
109
109
 
110
110
  ```ruby
111
- result.scores # => { EvaluatorClass => avg_score } across all scenarios
112
- result.scenario_results # => Array of ScenarioResult objects
113
- result.to_h # => Hash representation
111
+ result.scores # => { EvaluatorClass => avg_score } across all scenarios
112
+ result.scenario_results # => Array of ScenarioResult objects
113
+ result.token_usage # => TokenUsage the agent under test spent, summed across scenarios (nil if none reported)
114
+ result.evaluator_token_usage # => TokenUsage the judges spent, summed across scenarios (nil if no judge ran)
115
+ result.to_h # => Hash representation
114
116
  ```
115
117
 
116
118
  ### ScenarioResult
@@ -119,13 +121,15 @@ Each scenario produces a `Riffer::Evals::ScenarioResult`:
119
121
 
120
122
  ```ruby
121
123
  scenario = result.scenario_results.first
122
- scenario.input # => "What is the capital of France?"
123
- scenario.output # => "The capital of France is Paris."
124
- scenario.ground_truth # => "Paris"
125
- scenario.scores # => { EvaluatorClass => score } for this scenario
126
- scenario.results # => Array of Result objects
127
- scenario.messages # => Array of Message objects (system, user, assistant, tool)
128
- scenario.to_h # => Hash representation
124
+ scenario.input # => "What is the capital of France?"
125
+ scenario.output # => "The capital of France is Paris."
126
+ scenario.ground_truth # => "Paris"
127
+ scenario.scores # => { EvaluatorClass => score } for this scenario
128
+ scenario.results # => Array of Result objects
129
+ scenario.messages # => Array of Message objects (system, user, assistant, tool)
130
+ scenario.token_usage # => TokenUsage the agent under test spent on this scenario (nil if not reported)
131
+ scenario.evaluator_token_usage # => TokenUsage the judges spent on this scenario (nil if no judge ran)
132
+ scenario.to_h # => Hash representation
129
133
  ```
130
134
 
131
135
  ### Result
@@ -138,8 +142,13 @@ r.evaluator # => AnswerRelevancyEvaluator
138
142
  r.score # => 0.92
139
143
  r.reason # => "The response directly addresses..."
140
144
  r.higher_is_better # => true
145
+ r.token_usage # => TokenUsage for the judge call (nil for rule-based evaluators)
141
146
  ```
142
147
 
148
+ `token_usage` is a `Riffer::Providers::TokenUsage`, the same type the agent exposes on `Agent::Response`, so eval usage accumulates with the `+` operator just like agent usage. On a `Result` it's populated for LLM-as-judge evaluators and `nil` for rule-based ones that never call an LLM.
149
+
150
+ `ScenarioResult` and `RunResult` keep the two sources separate rather than exposing a single total: `token_usage` is what the agent under test spent generating the output, and `evaluator_token_usage` is what the judges spent scoring it. Add them yourself if you want a combined figure.
151
+
143
152
  ## Defining Custom Evaluators
144
153
 
145
154
  Create evaluators by subclassing `Riffer::Evals::Evaluator`. The simplest approach uses the `instructions` DSL — the base class handles calling the judge automatically:
@@ -190,7 +199,7 @@ Instance methods:
190
199
 
191
200
  - `evaluate(input:, output:, ground_truth:, messages:)` - Override for custom logic; default calls judge with `instructions`
192
201
  - `judge` - Returns a Judge instance for LLM-as-judge calls
193
- - `result(score:, reason:, metadata:)` - Helper to build Result objects
202
+ - `result(score:, reason:, metadata:, token_usage:)` - Helper to build Result objects
194
203
 
195
204
  ### Advanced: Custom Evaluate Override
196
205
 
@@ -66,7 +66,7 @@ class Riffer::Evals::Evaluator
66
66
  ground_truth: ground_truth
67
67
  )
68
68
 
69
- result(score: evaluation[:score], reason: evaluation[:reason])
69
+ result(score: evaluation[:score], reason: evaluation[:reason], token_usage: evaluation[:token_usage])
70
70
  end
71
71
 
72
72
  private
@@ -105,14 +105,15 @@ class Riffer::Evals::Evaluator
105
105
 
106
106
  # Builds a Result for this evaluator.
107
107
  #--
108
- #: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped]) -> Riffer::Evals::Result
109
- def result(score:, reason: nil, metadata: {})
108
+ #: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?token_usage: Riffer::Providers::TokenUsage?) -> Riffer::Evals::Result
109
+ def result(score:, reason: nil, metadata: {}, token_usage: nil)
110
110
  Riffer::Evals::Result.new(
111
111
  evaluator: self.class,
112
112
  score: score,
113
113
  reason: reason,
114
114
  metadata: metadata,
115
- higher_is_better: self.class.higher_is_better
115
+ higher_is_better: self.class.higher_is_better,
116
+ token_usage: token_usage
116
117
  )
117
118
  end
118
119
  end
@@ -62,6 +62,7 @@ module Riffer::Evals::EvaluatorRunner
62
62
  response = agent.generate(input, context: resolved_context)
63
63
  output = response.content
64
64
  messages = response.messages
65
+ token_usage = response.token_usage
65
66
 
66
67
  results = evaluators.map do |evaluator_class|
67
68
  evaluator_class.new.evaluate(input: input, output: output, ground_truth: ground_truth, messages: messages)
@@ -72,7 +73,8 @@ module Riffer::Evals::EvaluatorRunner
72
73
  output: output,
73
74
  ground_truth: ground_truth,
74
75
  results: results,
75
- messages: messages
76
+ messages: messages,
77
+ token_usage: token_usage
76
78
  )
77
79
  end
78
80
  end
@@ -121,7 +121,8 @@ class Riffer::Evals::Judge
121
121
 
122
122
  {
123
123
  score: score.to_f,
124
- reason: reason
124
+ reason: reason,
125
+ token_usage: response.token_usage
125
126
  }
126
127
  rescue JSON::ParserError => e
127
128
  raise Riffer::Error, "Invalid judge response: #{e.message}"
@@ -18,16 +18,21 @@ class Riffer::Evals::Result
18
18
  # Whether higher scores are better for this evaluator.
19
19
  attr_reader :higher_is_better #: bool
20
20
 
21
+ # Token usage for the judge call that produced this result, when the
22
+ # evaluator used an LLM. Nil for rule-based evaluators.
23
+ attr_reader :token_usage #: Riffer::Providers::TokenUsage?
24
+
21
25
  # Raises Riffer::ArgumentError if +score+ is not between 0.0 and 1.0.
22
26
  #--
23
- #: (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool) -> void
24
- def initialize(evaluator:, score:, reason: nil, metadata: {}, higher_is_better: true)
27
+ #: (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool, ?token_usage: Riffer::Providers::TokenUsage?) -> void
28
+ def initialize(evaluator:, score:, reason: nil, metadata: {}, higher_is_better: true, token_usage: nil)
25
29
  @evaluator = evaluator
26
30
  @score = score.to_f
27
31
  validate_score!
28
32
  @reason = reason
29
33
  @metadata = metadata
30
34
  @higher_is_better = higher_is_better
35
+ @token_usage = token_usage
31
36
  end
32
37
 
33
38
  # Returns a hash representation of the result.
@@ -40,7 +45,8 @@ class Riffer::Evals::Result
40
45
  score: score,
41
46
  reason: reason,
42
47
  metadata: metadata,
43
- higher_is_better: higher_is_better
48
+ higher_is_better: higher_is_better,
49
+ token_usage: token_usage&.to_h
44
50
  }
45
51
  end
46
52
 
@@ -35,6 +35,24 @@ class Riffer::Evals::RunResult
35
35
  end
36
36
  end
37
37
 
38
+ # Returns the summed token usage the agents under test spent across every
39
+ # scenario, or nil when none reported usage.
40
+ #
41
+ #--
42
+ #: () -> Riffer::Providers::TokenUsage?
43
+ def token_usage
44
+ scenario_results.map(&:token_usage).compact.reduce(:+)
45
+ end
46
+
47
+ # Returns the summed token usage across every scenario's LLM-as-judge
48
+ # evaluators, or nil when none reported usage.
49
+ #
50
+ #--
51
+ #: () -> Riffer::Providers::TokenUsage?
52
+ def evaluator_token_usage
53
+ scenario_results.map(&:evaluator_token_usage).compact.reduce(:+)
54
+ end
55
+
38
56
  # Returns a hash representation of the run result.
39
57
  #
40
58
  #--
@@ -42,7 +60,9 @@ class Riffer::Evals::RunResult
42
60
  def to_h
43
61
  {
44
62
  scores: scores.transform_keys(&:name),
45
- scenario_results: scenario_results.map(&:to_h)
63
+ scenario_results: scenario_results.map(&:to_h),
64
+ token_usage: token_usage&.to_h,
65
+ evaluator_token_usage: evaluator_token_usage&.to_h
46
66
  }
47
67
  end
48
68
  end
@@ -18,14 +18,18 @@ class Riffer::Evals::ScenarioResult
18
18
  # The full message history from the agent conversation.
19
19
  attr_reader :messages #: Array[Riffer::Messages::Base]
20
20
 
21
+ # Token usage the agent under test spent generating this scenario's output.
22
+ attr_reader :token_usage #: Riffer::Providers::TokenUsage?
23
+
21
24
  #--
22
- #: (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base]) -> void
23
- def initialize(input:, output:, ground_truth:, results:, messages: [])
25
+ #: (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base], ?token_usage: Riffer::Providers::TokenUsage?) -> void
26
+ def initialize(input:, output:, ground_truth:, results:, messages: [], token_usage: nil)
24
27
  @input = input
25
28
  @output = output
26
29
  @ground_truth = ground_truth
27
30
  @results = results
28
31
  @messages = messages
32
+ @token_usage = token_usage
29
33
  end
30
34
 
31
35
  # Returns scores keyed by evaluator class.
@@ -39,6 +43,15 @@ class Riffer::Evals::ScenarioResult
39
43
  end
40
44
  end
41
45
 
46
+ # Returns the summed token usage across this scenario's LLM-as-judge
47
+ # evaluators, or nil when none reported usage.
48
+ #
49
+ #--
50
+ #: () -> Riffer::Providers::TokenUsage?
51
+ def evaluator_token_usage
52
+ results.map(&:token_usage).compact.reduce(:+)
53
+ end
54
+
42
55
  # Returns a hash representation of the scenario result.
43
56
  #
44
57
  #--
@@ -50,7 +63,9 @@ class Riffer::Evals::ScenarioResult
50
63
  ground_truth: ground_truth,
51
64
  scores: scores.transform_keys(&:name),
52
65
  results: results.map(&:to_h),
53
- messages: messages.map(&:to_h)
66
+ messages: messages.map(&:to_h),
67
+ token_usage: token_usage&.to_h,
68
+ evaluator_token_usage: evaluator_token_usage&.to_h
54
69
  }
55
70
  end
56
71
  end
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.38.1" #: String
5
+ VERSION = "0.39.0" #: String
6
6
  end
@@ -57,6 +57,6 @@ class Riffer::Evals::Evaluator
57
57
 
58
58
  # Builds a Result for this evaluator.
59
59
  # --
60
- # : (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped]) -> Riffer::Evals::Result
61
- def result: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped]) -> Riffer::Evals::Result
60
+ # : (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?token_usage: Riffer::Providers::TokenUsage?) -> Riffer::Evals::Result
61
+ def result: (score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?token_usage: Riffer::Providers::TokenUsage?) -> Riffer::Evals::Result
62
62
  end
@@ -17,10 +17,14 @@ class Riffer::Evals::Result
17
17
  # Whether higher scores are better for this evaluator.
18
18
  attr_reader higher_is_better: bool
19
19
 
20
+ # Token usage for the judge call that produced this result, when the
21
+ # evaluator used an LLM. Nil for rule-based evaluators.
22
+ attr_reader token_usage: Riffer::Providers::TokenUsage?
23
+
20
24
  # Raises Riffer::ArgumentError if +score+ is not between 0.0 and 1.0.
21
25
  # --
22
- # : (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool) -> void
23
- def initialize: (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool) -> void
26
+ # : (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool, ?token_usage: Riffer::Providers::TokenUsage?) -> void
27
+ def initialize: (evaluator: singleton(Riffer::Evals::Evaluator), score: Float, ?reason: String?, ?metadata: Hash[Symbol, untyped], ?higher_is_better: bool, ?token_usage: Riffer::Providers::TokenUsage?) -> void
24
28
 
25
29
  # Returns a hash representation of the result.
26
30
  #
@@ -15,6 +15,20 @@ class Riffer::Evals::RunResult
15
15
  # : () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
16
16
  def scores: () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
17
17
 
18
+ # Returns the summed token usage the agents under test spent across every
19
+ # scenario, or nil when none reported usage.
20
+ #
21
+ # --
22
+ # : () -> Riffer::Providers::TokenUsage?
23
+ def token_usage: () -> Riffer::Providers::TokenUsage?
24
+
25
+ # Returns the summed token usage across every scenario's LLM-as-judge
26
+ # evaluators, or nil when none reported usage.
27
+ #
28
+ # --
29
+ # : () -> Riffer::Providers::TokenUsage?
30
+ def evaluator_token_usage: () -> Riffer::Providers::TokenUsage?
31
+
18
32
  # Returns a hash representation of the run result.
19
33
  #
20
34
  # --
@@ -17,9 +17,12 @@ class Riffer::Evals::ScenarioResult
17
17
  # The full message history from the agent conversation.
18
18
  attr_reader messages: Array[Riffer::Messages::Base]
19
19
 
20
+ # Token usage the agent under test spent generating this scenario's output.
21
+ attr_reader token_usage: Riffer::Providers::TokenUsage?
22
+
20
23
  # --
21
- # : (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base]) -> void
22
- def initialize: (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base]) -> void
24
+ # : (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base], ?token_usage: Riffer::Providers::TokenUsage?) -> void
25
+ def initialize: (input: String, output: String, ground_truth: String?, results: Array[Riffer::Evals::Result], ?messages: Array[Riffer::Messages::Base], ?token_usage: Riffer::Providers::TokenUsage?) -> void
23
26
 
24
27
  # Returns scores keyed by evaluator class.
25
28
  #
@@ -27,6 +30,13 @@ class Riffer::Evals::ScenarioResult
27
30
  # : () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
28
31
  def scores: () -> Hash[singleton(Riffer::Evals::Evaluator), Float]
29
32
 
33
+ # Returns the summed token usage across this scenario's LLM-as-judge
34
+ # evaluators, or nil when none reported usage.
35
+ #
36
+ # --
37
+ # : () -> Riffer::Providers::TokenUsage?
38
+ def evaluator_token_usage: () -> Riffer::Providers::TokenUsage?
39
+
30
40
  # Returns a hash representation of the scenario result.
31
41
  #
32
42
  # --
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.38.1
4
+ version: 0.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall