riffer 0.38.0 → 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: 201c994ac795b1563165552d686a38b103b83f718ceae14b102213ecb17984fd
4
- data.tar.gz: 933ede064f5446e7634a446c9cf8ad502c99728ee87f18498fdb564303f2dce1
3
+ metadata.gz: b1bf92e3bef065a4c07aadb273a7f6770b711c0f15f268726133c841cb3fd510
4
+ data.tar.gz: d7361c029b98ca819932e2c3d2d53113710bb434ad60092ee0578179fa844c5e
5
5
  SHA512:
6
- metadata.gz: 064e68be4a2c9916e6a33fed5952501722434396c82eed7da754cadaef509ecce764b67297f61bc1dca5e0cef1ec4447bb99013fb6cc4ac70dc74c274ecc746a
7
- data.tar.gz: fbeda57bbc9611d9f1be65f01560faf26e93c7d723e2e9116e6fb0d7c1f7b005488fd123c14f3917c4e73345b6554e7b10e8a7b104fe6bb5b65cf547f86de859
6
+ metadata.gz: 7f503377743f6629a1423234afb158570dc33ee225c82c0bc46e28938a839fe25b5bdfad6f61da274a8bf81e1e8dd6c505ad826593c5eb269251d47667fe9d7c
7
+ data.tar.gz: eddabf85e5416c777c3a9a441ce99cd1cb902ec1c339fa7338890ae8cb7e78c5e78628e1b886aea44cdc6228b99299fa3983816e10075d8c28b59229e4c0b376
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.38.0"
2
+ ".": "0.39.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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
+
15
+ ## [0.38.1](https://github.com/janeapp/riffer/compare/riffer/v0.38.0...riffer/v0.38.1) (2026-07-29)
16
+
17
+
18
+ ### Performance Improvements
19
+
20
+ * avoid O(n^2) text accumulation in streaming providers ([#378](https://github.com/janeapp/riffer/issues/378)) ([d344fa7](https://github.com/janeapp/riffer/commit/d344fa7122492d14ee1c91c0be3a88501170e255))
21
+
8
22
  ## [0.38.0](https://github.com/janeapp/riffer/compare/riffer/v0.37.1...riffer/v0.38.0) (2026-07-21)
9
23
 
10
24
 
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
 
@@ -121,7 +121,7 @@ module Riffer::Agent::Run
121
121
  #--
122
122
  #: (Riffer::Agent, Enumerator::Yielder, ?Hash[String, String]) -> Riffer::Messages::Assistant
123
123
  def accumulate_streamed_response(agent, stream_yielder, tags = {})
124
- accumulated_content = ""
124
+ accumulated_content = +""
125
125
  accumulated_tool_calls = [] #: Array[Riffer::Messages::Assistant::ToolCall]
126
126
  accumulated_token_usage = nil #: Riffer::Providers::TokenUsage?
127
127
  accumulated_finish_reason = nil #: Symbol?
@@ -131,9 +131,13 @@ module Riffer::Agent::Run
131
131
 
132
132
  case event
133
133
  when Riffer::StreamEvents::TextDelta
134
- accumulated_content += event.content
134
+ # Append in place rather than += (which reallocates and copies the whole
135
+ # buffer per delta, O(n^2) over a stream). accumulated_content stays an
136
+ # owned buffer; replace (not =) on TextDone keeps it that way so a later
137
+ # delta's << can never mutate the string held by a TextDone event.
138
+ accumulated_content << event.content
135
139
  when Riffer::StreamEvents::TextDone
136
- accumulated_content = event.content
140
+ accumulated_content.replace(event.content)
137
141
  when Riffer::StreamEvents::ToolCallDone
138
142
  accumulated_tool_calls << Riffer::Messages::Assistant::ToolCall.new(
139
143
  call_id: event.call_id,
@@ -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
@@ -267,7 +267,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
267
267
  state[:tool_call] = {
268
268
  id: typed_event.start.tool_use.tool_use_id,
269
269
  name: decode_tool_name(typed_event.start.tool_use.name, tools: @current_tools),
270
- arguments: ""
270
+ arguments: +""
271
271
  }
272
272
  end
273
273
 
@@ -276,8 +276,13 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
276
276
  def handle_content_block_delta_text_delta(event, state:, yielder:)
277
277
  typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
278
278
  delta_text = typed_event.delta.text
279
- state[:text] ||= ""
280
- state[:text] += delta_text
279
+ # Mutating append: += would reallocate and copy the whole accumulated
280
+ # buffer on every delta (O(n^2) per content block). state[:text] is handed
281
+ # off to TextDone and then cleared on block stop, so nothing reads the
282
+ # pre-append string, making in-place mutation safe. Seed with an unfrozen
283
+ # String (+"") so << does not raise under frozen_string_literal.
284
+ state[:text] ||= +""
285
+ state[:text] << delta_text
281
286
  yielder << Riffer::StreamEvents::TextDelta.new(delta_text)
282
287
  end
283
288
 
@@ -287,7 +292,7 @@ class Riffer::Providers::AmazonBedrock < Riffer::Providers::Base
287
292
  typed_event = event #: Aws::BedrockRuntime::Types::ContentBlockDeltaEvent
288
293
  input_delta = typed_event.delta.tool_use.input
289
294
 
290
- state[:tool_call][:arguments] += input_delta
295
+ state[:tool_call][:arguments] << input_delta
291
296
 
292
297
  yielder << Riffer::StreamEvents::ToolCallDelta.new(
293
298
  item_id: state[:tool_call][:id],
@@ -233,7 +233,7 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
233
233
  content_block = event.content_block
234
234
  if content_block.type.to_s == "server_tool_use" && content_block.name.to_s == "web_search"
235
235
  state[:web_search_index] = event.index
236
- state[:web_search_json] = ""
236
+ state[:web_search_json] = +""
237
237
  end
238
238
  end
239
239
 
@@ -243,22 +243,27 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
243
243
  return unless state[:web_search_index] == event.index
244
244
 
245
245
  delta = event.delta
246
- state[:web_search_json] += delta.partial_json if delta.respond_to?(:partial_json)
246
+ state[:web_search_json] << delta.partial_json if delta.respond_to?(:partial_json)
247
247
  end
248
248
 
249
249
  #--
250
250
  #: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
251
251
  def handle_text_event(event, state:, yielder:)
252
- state[:text] ||= ""
253
- state[:text] += event.text
252
+ # Mutating append instead of += to avoid reallocating and copying the whole
253
+ # accumulated buffer on every delta (O(n^2) per content block). The buffer
254
+ # is handed to TextDone and cleared on block stop, so no reader observes the
255
+ # pre-append string. Seed with an unfrozen String so << is legal under
256
+ # frozen_string_literal.
257
+ state[:text] ||= +""
258
+ state[:text] << event.text
254
259
  yielder << Riffer::StreamEvents::TextDelta.new(event.text)
255
260
  end
256
261
 
257
262
  #--
258
263
  #: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
259
264
  def handle_thinking_event(event, state:, yielder:)
260
- state[:reasoning] ||= ""
261
- state[:reasoning] += event.thinking
265
+ state[:reasoning] ||= +""
266
+ state[:reasoning] << event.thinking
262
267
  yielder << Riffer::StreamEvents::ReasoningDelta.new(event.thinking)
263
268
  end
264
269
 
@@ -266,9 +271,9 @@ class Riffer::Providers::Anthropic < Riffer::Providers::Base
266
271
  #: (untyped, state: Hash[Symbol, untyped], yielder: Riffer::Providers::_EventSink) -> void
267
272
  def handle_input_json_event(event, state:, yielder:)
268
273
  if state[:tool_call].nil?
269
- state[:tool_call] = {id: nil, name: nil, arguments: ""}
274
+ state[:tool_call] = {id: nil, name: nil, arguments: +""}
270
275
  end
271
- state[:tool_call][:arguments] += event.partial_json
276
+ state[:tool_call][:arguments] << event.partial_json
272
277
  yielder << Riffer::StreamEvents::ToolCallDelta.new(
273
278
  item_id: state[:tool_call][:id] || "pending",
274
279
  name: state[:tool_call][:name],
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Riffer
5
- VERSION = "0.38.0" #: 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.0
4
+ version: 0.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -35,14 +35,14 @@ dependencies:
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: 1.56.0
38
+ version: 1.59.0
39
39
  type: :development
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - "~>"
44
44
  - !ruby/object:Gem::Version
45
- version: 1.56.0
45
+ version: 1.59.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: aws-sdk-bedrockruntime
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -77,28 +77,28 @@ dependencies:
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '0.8'
80
+ version: '1.0'
81
81
  type: :development
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '0.8'
87
+ version: '1.0'
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: openai
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: 0.71.0
94
+ version: 0.72.0
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: 0.71.0
101
+ version: 0.72.0
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: async
104
104
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +108,7 @@ dependencies:
108
108
  version: '2.25'
109
109
  - - "<"
110
110
  - !ruby/object:Gem::Version
111
- version: '2.43'
111
+ version: '2.44'
112
112
  type: :development
113
113
  prerelease: false
114
114
  version_requirements: !ruby/object:Gem::Requirement
@@ -118,7 +118,7 @@ dependencies:
118
118
  version: '2.25'
119
119
  - - "<"
120
120
  - !ruby/object:Gem::Version
121
- version: '2.43'
121
+ version: '2.44'
122
122
  - !ruby/object:Gem::Dependency
123
123
  name: io-event
124
124
  requirement: !ruby/object:Gem::Requirement