ruby_llm-agents 0.2.4 → 0.3.1
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/README.md +413 -0
- data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
- data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
- data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
- data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
- data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
- data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
- data/app/models/ruby_llm/agents/execution.rb +259 -16
- data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
- data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
- data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
- data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
- data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
- data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
- data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
- data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
- data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
- data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
- data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
- data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
- data/config/routes.rb +7 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +143 -8
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
- data/lib/ruby_llm/agents/alert_manager.rb +207 -0
- data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
- data/lib/ruby_llm/agents/base.rb +597 -112
- data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
- data/lib/ruby_llm/agents/configuration.rb +279 -1
- data/lib/ruby_llm/agents/engine.rb +58 -6
- data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
- data/lib/ruby_llm/agents/inflections.rb +13 -2
- data/lib/ruby_llm/agents/instrumentation.rb +538 -87
- data/lib/ruby_llm/agents/redactor.rb +130 -0
- data/lib/ruby_llm/agents/reliability.rb +185 -0
- data/lib/ruby_llm/agents/version.rb +3 -1
- data/lib/ruby_llm/agents.rb +52 -0
- metadata +41 -2
- data/app/controllers/ruby_llm/agents/application_controller.rb +0 -37
|
@@ -5,18 +5,28 @@ module RubyLLM
|
|
|
5
5
|
class Execution
|
|
6
6
|
# Analytics concern for advanced reporting and analysis
|
|
7
7
|
#
|
|
8
|
-
# Provides class methods for
|
|
9
|
-
#
|
|
10
|
-
# - Cost breakdown by agent type
|
|
11
|
-
# - Performance stats for specific agents
|
|
12
|
-
# - Version comparison
|
|
13
|
-
# - Trend analysis over time
|
|
8
|
+
# Provides class methods for generating reports, analyzing trends,
|
|
9
|
+
# comparing versions, and building chart data.
|
|
14
10
|
#
|
|
11
|
+
# @see RubyLLM::Agents::Execution::Scopes
|
|
12
|
+
# @api public
|
|
15
13
|
module Analytics
|
|
16
14
|
extend ActiveSupport::Concern
|
|
17
15
|
|
|
18
16
|
class_methods do
|
|
19
|
-
#
|
|
17
|
+
# Generates a daily report with key metrics for today
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] Report containing:
|
|
20
|
+
# - :date [Date] Current date
|
|
21
|
+
# - :total_executions [Integer] Total execution count
|
|
22
|
+
# - :successful [Integer] Successful execution count
|
|
23
|
+
# - :failed [Integer] Failed execution count
|
|
24
|
+
# - :total_cost [Float] Sum of all costs
|
|
25
|
+
# - :total_tokens [Integer] Sum of all tokens
|
|
26
|
+
# - :avg_duration_ms [Integer] Average duration
|
|
27
|
+
# - :error_rate [Float] Percentage of failures
|
|
28
|
+
# - :by_agent [Hash] Counts grouped by agent type
|
|
29
|
+
# - :top_errors [Hash] Top 5 error classes
|
|
20
30
|
def daily_report
|
|
21
31
|
scope = today
|
|
22
32
|
|
|
@@ -35,7 +45,10 @@ module RubyLLM
|
|
|
35
45
|
}
|
|
36
46
|
end
|
|
37
47
|
|
|
38
|
-
#
|
|
48
|
+
# Returns cost breakdown grouped by agent type
|
|
49
|
+
#
|
|
50
|
+
# @param period [Symbol] Time scope (:today, :this_week, :this_month, :all_time)
|
|
51
|
+
# @return [Hash{String => Float}] Agent types mapped to total cost, sorted descending
|
|
39
52
|
def cost_by_agent(period: :today)
|
|
40
53
|
public_send(period)
|
|
41
54
|
.group(:agent_type)
|
|
@@ -44,7 +57,11 @@ module RubyLLM
|
|
|
44
57
|
.to_h
|
|
45
58
|
end
|
|
46
59
|
|
|
47
|
-
#
|
|
60
|
+
# Returns performance statistics for a specific agent
|
|
61
|
+
#
|
|
62
|
+
# @param agent_type [String] The agent class name
|
|
63
|
+
# @param period [Symbol] Time scope (:today, :this_week, :this_month, :all_time)
|
|
64
|
+
# @return [Hash] Statistics including count, costs, tokens, duration, rates
|
|
48
65
|
def stats_for(agent_type, period: :today)
|
|
49
66
|
scope = by_agent(agent_type).public_send(period)
|
|
50
67
|
count = scope.count
|
|
@@ -64,7 +81,13 @@ module RubyLLM
|
|
|
64
81
|
}
|
|
65
82
|
end
|
|
66
83
|
|
|
67
|
-
#
|
|
84
|
+
# Compares performance between two agent versions
|
|
85
|
+
#
|
|
86
|
+
# @param agent_type [String] The agent class name
|
|
87
|
+
# @param version1 [String] First version to compare (baseline)
|
|
88
|
+
# @param version2 [String] Second version to compare
|
|
89
|
+
# @param period [Symbol] Time scope for comparison
|
|
90
|
+
# @return [Hash] Comparison data with stats for each version and improvement percentages
|
|
68
91
|
def compare_versions(agent_type, version1, version2, period: :this_week)
|
|
69
92
|
base_scope = by_agent(agent_type).public_send(period)
|
|
70
93
|
|
|
@@ -84,7 +107,38 @@ module RubyLLM
|
|
|
84
107
|
}
|
|
85
108
|
end
|
|
86
109
|
|
|
87
|
-
#
|
|
110
|
+
# Returns daily trend data for a specific agent version
|
|
111
|
+
#
|
|
112
|
+
# Used for sparkline charts in version comparison.
|
|
113
|
+
#
|
|
114
|
+
# @param agent_type [String] The agent class name
|
|
115
|
+
# @param version [String] The version to analyze
|
|
116
|
+
# @param days [Integer] Number of days to analyze
|
|
117
|
+
# @return [Array<Hash>] Daily metrics sorted oldest to newest
|
|
118
|
+
def version_trend_data(agent_type, version, days: 14)
|
|
119
|
+
scope = by_agent(agent_type).by_version(version)
|
|
120
|
+
|
|
121
|
+
(0...days).map do |days_ago|
|
|
122
|
+
date = days_ago.days.ago.to_date
|
|
123
|
+
day_scope = scope.where(created_at: date.beginning_of_day..date.end_of_day)
|
|
124
|
+
count = day_scope.count
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
date: date,
|
|
128
|
+
count: count,
|
|
129
|
+
success_rate: calculate_success_rate(day_scope),
|
|
130
|
+
avg_cost: count > 0 ? ((day_scope.total_cost_sum || 0) / count).round(6) : 0,
|
|
131
|
+
avg_duration_ms: day_scope.avg_duration&.round || 0,
|
|
132
|
+
avg_tokens: day_scope.avg_tokens&.round || 0
|
|
133
|
+
}
|
|
134
|
+
end.reverse
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Analyzes trends over a time period
|
|
138
|
+
#
|
|
139
|
+
# @param agent_type [String, nil] Filter to specific agent, or nil for all
|
|
140
|
+
# @param days [Integer] Number of days to analyze
|
|
141
|
+
# @return [Array<Hash>] Daily metrics sorted oldest to newest
|
|
88
142
|
def trend_analysis(agent_type: nil, days: 7)
|
|
89
143
|
scope = agent_type ? by_agent(agent_type) : all
|
|
90
144
|
|
|
@@ -102,11 +156,96 @@ module RubyLLM
|
|
|
102
156
|
end.reverse
|
|
103
157
|
end
|
|
104
158
|
|
|
105
|
-
#
|
|
159
|
+
# Builds hourly activity chart data for today
|
|
160
|
+
#
|
|
161
|
+
# Cached for 5 minutes to reduce database load.
|
|
162
|
+
#
|
|
163
|
+
# @return [Array<Hash>] Chart series with success and failed counts per hour
|
|
106
164
|
def hourly_activity_chart
|
|
165
|
+
# No caching - always fresh data based on latest execution
|
|
166
|
+
build_hourly_activity_data
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Returns chart data as arrays for Highcharts live updates
|
|
170
|
+
# Format: { categories: [...], series: [...] }
|
|
171
|
+
def hourly_activity_chart_json
|
|
172
|
+
# Always use current time as reference so chart shows "now" on the right
|
|
173
|
+
reference_time = Time.current.beginning_of_hour
|
|
174
|
+
|
|
175
|
+
categories = []
|
|
176
|
+
success_data = []
|
|
177
|
+
failed_data = []
|
|
178
|
+
|
|
179
|
+
# Create entries for the last 24 hours ending at current hour
|
|
180
|
+
(23.downto(0)).each do |hours_ago|
|
|
181
|
+
start_time = reference_time - hours_ago.hours
|
|
182
|
+
end_time = start_time + 1.hour
|
|
183
|
+
categories << start_time.in_time_zone.strftime("%H:%M")
|
|
184
|
+
|
|
185
|
+
hour_scope = where(created_at: start_time...end_time)
|
|
186
|
+
success_data << hour_scope.successful.count
|
|
187
|
+
failed_data << hour_scope.failed.count
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
{
|
|
191
|
+
categories: categories,
|
|
192
|
+
series: [
|
|
193
|
+
{ name: "Success", data: success_data },
|
|
194
|
+
{ name: "Failed", data: failed_data }
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Builds the hourly activity data structure
|
|
200
|
+
# Shows the last 24 hours with current hour on the right
|
|
201
|
+
#
|
|
202
|
+
# @return [Array<Hash>] Success and failed series data
|
|
203
|
+
# @api private
|
|
204
|
+
def build_hourly_activity_data
|
|
107
205
|
success_data = {}
|
|
108
206
|
failed_data = {}
|
|
109
207
|
|
|
208
|
+
# Use current time as reference so chart shows "now" on the right
|
|
209
|
+
reference_time = Time.current.beginning_of_hour
|
|
210
|
+
|
|
211
|
+
# Create entries for the last 24 hours ending at current hour
|
|
212
|
+
(23.downto(0)).each do |hours_ago|
|
|
213
|
+
start_time = reference_time - hours_ago.hours
|
|
214
|
+
end_time = start_time + 1.hour
|
|
215
|
+
time_label = start_time.in_time_zone.strftime("%H:%M")
|
|
216
|
+
|
|
217
|
+
hour_scope = where(created_at: start_time...end_time)
|
|
218
|
+
success_data[time_label] = hour_scope.successful.count
|
|
219
|
+
failed_data[time_label] = hour_scope.failed.count
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
[
|
|
223
|
+
{ name: "Success", data: success_data },
|
|
224
|
+
{ name: "Failed", data: failed_data }
|
|
225
|
+
]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Retrieves hourly cost data for chart display
|
|
229
|
+
#
|
|
230
|
+
# Returns two series (input cost and output cost) with hourly breakdowns
|
|
231
|
+
# for the current day. Results are cached for 5 minutes.
|
|
232
|
+
#
|
|
233
|
+
# @return [Array<Hash>] Chart series with input and output cost per hour
|
|
234
|
+
def hourly_cost_chart
|
|
235
|
+
cache_key = "ruby_llm_agents/hourly_cost/#{Date.current}"
|
|
236
|
+
Rails.cache.fetch(cache_key, expires_in: 5.minutes) do
|
|
237
|
+
build_hourly_cost_data
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Builds the hourly cost data structure (uncached)
|
|
242
|
+
#
|
|
243
|
+
# @return [Array<Hash>] Input and output cost series data
|
|
244
|
+
# @api private
|
|
245
|
+
def build_hourly_cost_data
|
|
246
|
+
input_cost_data = {}
|
|
247
|
+
output_cost_data = {}
|
|
248
|
+
|
|
110
249
|
# Create entries for each hour of the day (0-23)
|
|
111
250
|
(0..23).each do |hour|
|
|
112
251
|
time_label = format("%02d:00", hour)
|
|
@@ -114,33 +253,93 @@ module RubyLLM
|
|
|
114
253
|
end_time = start_time + 1.hour
|
|
115
254
|
|
|
116
255
|
hour_scope = where(created_at: start_time...end_time)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
success_data[time_label] = total - failed
|
|
121
|
-
failed_data[time_label] = failed
|
|
256
|
+
input_cost_data[time_label] = (hour_scope.sum(:input_cost) || 0).round(6)
|
|
257
|
+
output_cost_data[time_label] = (hour_scope.sum(:output_cost) || 0).round(6)
|
|
122
258
|
end
|
|
123
259
|
|
|
124
260
|
[
|
|
125
|
-
{ name: "
|
|
126
|
-
{ name: "
|
|
261
|
+
{ name: "Input Cost", data: input_cost_data },
|
|
262
|
+
{ name: "Output Cost", data: output_cost_data }
|
|
127
263
|
]
|
|
128
264
|
end
|
|
129
265
|
|
|
130
|
-
|
|
266
|
+
# Cache hit rate percentage
|
|
267
|
+
#
|
|
268
|
+
# @return [Float] Percentage of executions that were cache hits (0.0-100.0)
|
|
269
|
+
def cache_hit_rate
|
|
270
|
+
total = count
|
|
271
|
+
return 0.0 if total.zero?
|
|
272
|
+
|
|
273
|
+
(cached.count.to_f / total * 100).round(1)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Streaming execution rate percentage
|
|
277
|
+
#
|
|
278
|
+
# @return [Float] Percentage of executions that used streaming (0.0-100.0)
|
|
279
|
+
def streaming_rate
|
|
280
|
+
total = count
|
|
281
|
+
return 0.0 if total.zero?
|
|
282
|
+
|
|
283
|
+
(streaming.count.to_f / total * 100).round(1)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Average time to first token for streaming executions
|
|
287
|
+
#
|
|
288
|
+
# @return [Integer, nil] Average TTFT in milliseconds, or nil if no data
|
|
289
|
+
def avg_time_to_first_token
|
|
290
|
+
streaming.where.not(time_to_first_token_ms: nil).average(:time_to_first_token_ms)&.round(0)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Finish reason distribution
|
|
294
|
+
#
|
|
295
|
+
# @return [Hash{String => Integer}] Counts grouped by finish reason, sorted descending
|
|
296
|
+
def finish_reason_distribution
|
|
297
|
+
group(:finish_reason).count.sort_by { |_, v| -v }.to_h
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Rate limited execution count
|
|
301
|
+
#
|
|
302
|
+
# @return [Integer] Number of executions that were rate limited
|
|
303
|
+
def rate_limited_count
|
|
304
|
+
where(rate_limited: true).count
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Rate limited rate percentage
|
|
308
|
+
#
|
|
309
|
+
# @return [Float] Percentage of executions that were rate limited (0.0-100.0)
|
|
310
|
+
def rate_limited_rate
|
|
311
|
+
total = count
|
|
312
|
+
return 0.0 if total.zero?
|
|
313
|
+
|
|
314
|
+
(rate_limited_count.to_f / total * 100).round(1)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
private
|
|
131
318
|
|
|
319
|
+
# Calculates success rate percentage for a scope
|
|
320
|
+
#
|
|
321
|
+
# @param scope [ActiveRecord::Relation] The scope to calculate from
|
|
322
|
+
# @return [Float] Success rate as percentage (0.0-100.0)
|
|
132
323
|
def calculate_success_rate(scope)
|
|
133
324
|
total = scope.count
|
|
134
325
|
return 0.0 if total.zero?
|
|
135
326
|
(scope.successful.count.to_f / total * 100).round(2)
|
|
136
327
|
end
|
|
137
328
|
|
|
329
|
+
# Calculates error rate percentage for a scope
|
|
330
|
+
#
|
|
331
|
+
# @param scope [ActiveRecord::Relation] The scope to calculate from
|
|
332
|
+
# @return [Float] Error rate as percentage (0.0-100.0)
|
|
138
333
|
def calculate_error_rate(scope)
|
|
139
334
|
total = scope.count
|
|
140
335
|
return 0.0 if total.zero?
|
|
141
336
|
(scope.failed.count.to_f / total * 100).round(2)
|
|
142
337
|
end
|
|
143
338
|
|
|
339
|
+
# Calculates statistics for an arbitrary scope
|
|
340
|
+
#
|
|
341
|
+
# @param scope [ActiveRecord::Relation] The scope to analyze
|
|
342
|
+
# @return [Hash] Statistics hash
|
|
144
343
|
def stats_for_scope(scope)
|
|
145
344
|
count = scope.count
|
|
146
345
|
total_cost = scope.total_cost_sum || 0
|
|
@@ -155,6 +354,11 @@ module RubyLLM
|
|
|
155
354
|
}
|
|
156
355
|
end
|
|
157
356
|
|
|
357
|
+
# Calculates percentage change between two values
|
|
358
|
+
#
|
|
359
|
+
# @param old_value [Numeric, nil] Baseline value
|
|
360
|
+
# @param new_value [Numeric] New value
|
|
361
|
+
# @return [Float] Percentage change (negative = improvement for costs/duration)
|
|
158
362
|
def percent_change(old_value, new_value)
|
|
159
363
|
return 0.0 if old_value.nil? || old_value.zero?
|
|
160
364
|
((new_value - old_value).to_f / old_value * 100).round(2)
|
|
@@ -5,16 +5,22 @@ module RubyLLM
|
|
|
5
5
|
class Execution
|
|
6
6
|
# Metrics concern for cost calculations and performance metrics
|
|
7
7
|
#
|
|
8
|
-
# Provides methods for
|
|
9
|
-
#
|
|
10
|
-
# - Human-readable duration formatting
|
|
11
|
-
# - Performance metrics (tokens/second, cost per 1K tokens)
|
|
12
|
-
# - Formatted cost display helpers
|
|
8
|
+
# Provides instance methods for calculating costs from token usage,
|
|
9
|
+
# formatting durations, and computing performance metrics.
|
|
13
10
|
#
|
|
11
|
+
# @see RubyLLM::Agents::Execution::Analytics
|
|
12
|
+
# @api public
|
|
14
13
|
module Metrics
|
|
15
14
|
extend ActiveSupport::Concern
|
|
16
15
|
|
|
17
|
-
#
|
|
16
|
+
# Calculates and sets input/output costs from token usage
|
|
17
|
+
#
|
|
18
|
+
# Uses RubyLLM's built-in pricing data to calculate costs.
|
|
19
|
+
# Sets input_cost and output_cost attributes (total_cost is calculated by callback).
|
|
20
|
+
#
|
|
21
|
+
# @param model_info [RubyLLM::Model, nil] Optional pre-resolved model info
|
|
22
|
+
# @return [void]
|
|
23
|
+
# @note Requires input_tokens and output_tokens to be set
|
|
18
24
|
def calculate_costs!(model_info = nil)
|
|
19
25
|
return unless input_tokens && output_tokens
|
|
20
26
|
|
|
@@ -30,55 +36,65 @@ module RubyLLM
|
|
|
30
36
|
self.output_cost = ((output_tokens / 1_000_000.0) * output_price_per_million).round(6)
|
|
31
37
|
end
|
|
32
38
|
|
|
33
|
-
#
|
|
39
|
+
# Returns execution duration in seconds
|
|
40
|
+
#
|
|
41
|
+
# @return [Float, nil] Duration in seconds with 2 decimal places, or nil
|
|
34
42
|
def duration_seconds
|
|
35
43
|
duration_ms ? (duration_ms / 1000.0).round(2) : nil
|
|
36
44
|
end
|
|
37
45
|
|
|
38
|
-
#
|
|
46
|
+
# Calculates throughput as tokens processed per second
|
|
47
|
+
#
|
|
48
|
+
# @return [Float, nil] Tokens per second, or nil if data unavailable
|
|
39
49
|
def tokens_per_second
|
|
40
50
|
return nil unless duration_ms && duration_ms > 0 && total_tokens
|
|
41
51
|
(total_tokens / duration_seconds.to_f).round(2)
|
|
42
52
|
end
|
|
43
53
|
|
|
44
|
-
#
|
|
54
|
+
# Calculates cost efficiency as cost per 1,000 tokens
|
|
55
|
+
#
|
|
56
|
+
# Useful for comparing cost efficiency across different models.
|
|
57
|
+
#
|
|
58
|
+
# @return [Float, nil] Cost per 1K tokens in USD, or nil if data unavailable
|
|
45
59
|
def cost_per_1k_tokens
|
|
46
60
|
return nil unless total_tokens && total_tokens > 0 && total_cost
|
|
47
61
|
(total_cost / total_tokens.to_f * 1000).round(6)
|
|
48
62
|
end
|
|
49
63
|
|
|
50
|
-
#
|
|
51
|
-
# Cost Display Helpers
|
|
52
|
-
# ==============================================================================
|
|
53
|
-
#
|
|
54
|
-
# Format cost as currency string
|
|
55
|
-
# Example: format_cost(0.000045) => "$0.000045"
|
|
56
|
-
#
|
|
64
|
+
# @!group Cost Display Helpers
|
|
57
65
|
|
|
66
|
+
# Returns input_cost formatted as currency
|
|
67
|
+
#
|
|
68
|
+
# @return [String, nil] Formatted cost (e.g., "$0.000045") or nil
|
|
58
69
|
def formatted_input_cost
|
|
59
70
|
format_cost(input_cost)
|
|
60
71
|
end
|
|
61
72
|
|
|
73
|
+
# Returns output_cost formatted as currency
|
|
74
|
+
#
|
|
75
|
+
# @return [String, nil] Formatted cost (e.g., "$0.000045") or nil
|
|
62
76
|
def formatted_output_cost
|
|
63
77
|
format_cost(output_cost)
|
|
64
78
|
end
|
|
65
79
|
|
|
80
|
+
# Returns total_cost formatted as currency
|
|
81
|
+
#
|
|
82
|
+
# @return [String, nil] Formatted cost (e.g., "$0.000045") or nil
|
|
66
83
|
def formatted_total_cost
|
|
67
84
|
format_cost(total_cost)
|
|
68
85
|
end
|
|
69
86
|
|
|
70
|
-
|
|
87
|
+
# @!endgroup
|
|
71
88
|
|
|
72
|
-
|
|
73
|
-
return nil unless model_id
|
|
89
|
+
private
|
|
74
90
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
rescue RubyLLM::ModelNotFoundError
|
|
78
|
-
Rails.logger.warn("[RubyLLM::Agents] Model not found for pricing: #{model_id}")
|
|
79
|
-
nil
|
|
80
|
-
end
|
|
91
|
+
# NOTE: resolve_model_info is defined in Execution class (execution.rb)
|
|
92
|
+
# It accepts an optional model_id parameter, defaulting to self.model_id
|
|
81
93
|
|
|
94
|
+
# Formats a cost value as currency string
|
|
95
|
+
#
|
|
96
|
+
# @param cost [Float, nil] Cost in USD
|
|
97
|
+
# @return [String, nil] Formatted string or nil
|
|
82
98
|
def format_cost(cost)
|
|
83
99
|
return nil unless cost
|
|
84
100
|
format("$%.6f", cost)
|