ruby_llm-agents 1.3.3 → 2.0.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 +4 -4
- data/README.md +101 -334
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +46 -10
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +52 -6
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +58 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +12 -81
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -82,58 +82,6 @@ module RubyLLM
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
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
|
|
91
|
-
def compare_versions(agent_type, version1, version2, period: :this_week)
|
|
92
|
-
base_scope = by_agent(agent_type).public_send(period)
|
|
93
|
-
|
|
94
|
-
v1_stats = stats_for_scope(base_scope.by_version(version1))
|
|
95
|
-
v2_stats = stats_for_scope(base_scope.by_version(version2))
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
agent_type: agent_type,
|
|
99
|
-
period: period,
|
|
100
|
-
version1: { version: version1, **v1_stats },
|
|
101
|
-
version2: { version: version2, **v2_stats },
|
|
102
|
-
improvements: {
|
|
103
|
-
cost_change_pct: percent_change(v1_stats[:avg_cost], v2_stats[:avg_cost]),
|
|
104
|
-
token_change_pct: percent_change(v1_stats[:avg_tokens], v2_stats[:avg_tokens]),
|
|
105
|
-
speed_change_pct: percent_change(v1_stats[:avg_duration_ms], v2_stats[:avg_duration_ms])
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
end
|
|
109
|
-
|
|
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
85
|
# Analyzes trends over a time period
|
|
138
86
|
#
|
|
139
87
|
# @param agent_type [String, nil] Filter to specific agent, or nil for all
|
|
@@ -526,9 +474,18 @@ module RubyLLM
|
|
|
526
474
|
|
|
527
475
|
# Average time to first token for streaming executions
|
|
528
476
|
#
|
|
477
|
+
# time_to_first_token_ms is stored in metadata JSON, so we use
|
|
478
|
+
# Ruby-level calculation instead of SQL aggregation.
|
|
479
|
+
#
|
|
529
480
|
# @return [Integer, nil] Average TTFT in milliseconds, or nil if no data
|
|
530
481
|
def avg_time_to_first_token
|
|
531
|
-
streaming
|
|
482
|
+
ttft_values = streaming
|
|
483
|
+
.where("metadata IS NOT NULL")
|
|
484
|
+
.pluck(:metadata)
|
|
485
|
+
.filter_map { |m| m&.dig("time_to_first_token_ms") }
|
|
486
|
+
return nil if ttft_values.empty?
|
|
487
|
+
|
|
488
|
+
(ttft_values.sum.to_f / ttft_values.size).round(0)
|
|
532
489
|
end
|
|
533
490
|
|
|
534
491
|
# Finish reason distribution
|
|
@@ -540,9 +497,11 @@ module RubyLLM
|
|
|
540
497
|
|
|
541
498
|
# Rate limited execution count
|
|
542
499
|
#
|
|
500
|
+
# rate_limited is stored in metadata JSON
|
|
501
|
+
#
|
|
543
502
|
# @return [Integer] Number of executions that were rate limited
|
|
544
503
|
def rate_limited_count
|
|
545
|
-
|
|
504
|
+
metadata_true("rate_limited").count
|
|
546
505
|
end
|
|
547
506
|
|
|
548
507
|
# Rate limited rate percentage
|
|
@@ -79,17 +79,11 @@ module RubyLLM
|
|
|
79
79
|
# @param agent_type [String] The agent class name
|
|
80
80
|
# @return [ActiveRecord::Relation]
|
|
81
81
|
|
|
82
|
-
# @!method by_version(version)
|
|
83
|
-
# Filters to a specific agent version
|
|
84
|
-
# @param version [String] The version string
|
|
85
|
-
# @return [ActiveRecord::Relation]
|
|
86
|
-
|
|
87
82
|
# @!method by_model(model_id)
|
|
88
83
|
# Filters to a specific LLM model
|
|
89
84
|
# @param model_id [String] The model identifier
|
|
90
85
|
# @return [ActiveRecord::Relation]
|
|
91
86
|
scope :by_agent, ->(agent_type) { where(agent_type: agent_type.to_s) }
|
|
92
|
-
scope :by_version, ->(version) { where(agent_version: version.to_s) }
|
|
93
87
|
scope :by_model, ->(model_id) { where(model_id: model_id.to_s) }
|
|
94
88
|
|
|
95
89
|
# @!endgroup
|
|
@@ -142,15 +136,25 @@ module RubyLLM
|
|
|
142
136
|
# @!group Parameter Scopes
|
|
143
137
|
|
|
144
138
|
# @!method with_parameter(key, value = nil)
|
|
145
|
-
# Filters by
|
|
139
|
+
# Filters by parameter key/value in the execution_details table
|
|
146
140
|
# @param key [String, Symbol] Parameter key to check
|
|
147
141
|
# @param value [Object, nil] Optional value to match
|
|
148
142
|
# @return [ActiveRecord::Relation]
|
|
149
143
|
scope :with_parameter, ->(key, value = nil) do
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
detail_table = RubyLLM::Agents::ExecutionDetail.table_name
|
|
145
|
+
joined = joins(:detail)
|
|
146
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
147
|
+
if value
|
|
148
|
+
joined.where("json_extract(#{detail_table}.parameters, ?) = ?", "$.#{key}", value.to_s)
|
|
149
|
+
else
|
|
150
|
+
joined.where("json_extract(#{detail_table}.parameters, ?) IS NOT NULL", "$.#{key}")
|
|
151
|
+
end
|
|
152
152
|
else
|
|
153
|
-
|
|
153
|
+
if value
|
|
154
|
+
joined.where("#{detail_table}.parameters @> ?", { key => value }.to_json)
|
|
155
|
+
else
|
|
156
|
+
joined.where("#{detail_table}.parameters ? :key", key: key.to_s)
|
|
157
|
+
end
|
|
154
158
|
end
|
|
155
159
|
end
|
|
156
160
|
|
|
@@ -196,10 +200,12 @@ module RubyLLM
|
|
|
196
200
|
# @!method rate_limited
|
|
197
201
|
# Returns executions that were rate limited
|
|
198
202
|
# @return [ActiveRecord::Relation]
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
scope :
|
|
202
|
-
scope :
|
|
203
|
+
#
|
|
204
|
+
# Note: fallback_reason, retryable, and rate_limited are stored in metadata JSON
|
|
205
|
+
scope :with_fallback, -> { metadata_present("fallback_reason") }
|
|
206
|
+
scope :retryable_errors, -> { metadata_true("retryable") }
|
|
207
|
+
scope :rate_limited, -> { metadata_true("rate_limited") }
|
|
208
|
+
scope :by_fallback_reason, ->(reason) { metadata_value("fallback_reason", reason) }
|
|
203
209
|
|
|
204
210
|
# @!endgroup
|
|
205
211
|
|
|
@@ -269,6 +275,47 @@ module RubyLLM
|
|
|
269
275
|
# They can be called on scoped relations.
|
|
270
276
|
|
|
271
277
|
class_methods do
|
|
278
|
+
# Database-agnostic JSON metadata queries
|
|
279
|
+
# These fields (fallback_reason, retryable, rate_limited, etc.) are stored
|
|
280
|
+
# in the metadata JSON column rather than as direct columns.
|
|
281
|
+
|
|
282
|
+
# Queries for metadata key presence (IS NOT NULL)
|
|
283
|
+
#
|
|
284
|
+
# @param key [String] The metadata key
|
|
285
|
+
# @return [ActiveRecord::Relation]
|
|
286
|
+
def metadata_present(key)
|
|
287
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
288
|
+
where("json_extract(metadata, ?) IS NOT NULL", "$.#{key}")
|
|
289
|
+
else
|
|
290
|
+
where("metadata->>? IS NOT NULL", key.to_s)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Queries for metadata boolean value being true
|
|
295
|
+
#
|
|
296
|
+
# @param key [String] The metadata key
|
|
297
|
+
# @return [ActiveRecord::Relation]
|
|
298
|
+
def metadata_true(key)
|
|
299
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
300
|
+
where("json_extract(metadata, ?) = 1", "$.#{key}")
|
|
301
|
+
else
|
|
302
|
+
where("metadata @> ?", { key.to_s => true }.to_json)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Queries for metadata key matching a specific value
|
|
307
|
+
#
|
|
308
|
+
# @param key [String] The metadata key
|
|
309
|
+
# @param value [Object] The value to match
|
|
310
|
+
# @return [ActiveRecord::Relation]
|
|
311
|
+
def metadata_value(key, value)
|
|
312
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
313
|
+
where("json_extract(metadata, ?) = ?", "$.#{key}", value.to_s)
|
|
314
|
+
else
|
|
315
|
+
where("metadata->>? = ?", key.to_s, value.to_s)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
272
319
|
# Returns sum of total_cost for the current scope
|
|
273
320
|
#
|
|
274
321
|
# @return [Float, nil] Total cost in USD
|
|
@@ -8,8 +8,6 @@ module RubyLLM
|
|
|
8
8
|
#
|
|
9
9
|
# @!attribute [rw] agent_type
|
|
10
10
|
# @return [String] Full class name of the agent (e.g., "SearchAgent")
|
|
11
|
-
# @!attribute [rw] agent_version
|
|
12
|
-
# @return [String] Version string for cache invalidation
|
|
13
11
|
# @!attribute [rw] model_id
|
|
14
12
|
# @return [String] LLM model identifier used
|
|
15
13
|
# @!attribute [rw] temperature
|
|
@@ -37,7 +35,7 @@ module RubyLLM
|
|
|
37
35
|
# @!attribute [rw] parameters
|
|
38
36
|
# @return [Hash] Sanitized parameters passed to the agent
|
|
39
37
|
# @!attribute [rw] metadata
|
|
40
|
-
# @return [Hash] Custom metadata from
|
|
38
|
+
# @return [Hash] Custom metadata from metadata hook
|
|
41
39
|
# @!attribute [rw] error_class
|
|
42
40
|
# @return [String, nil] Exception class name if failed
|
|
43
41
|
# @!attribute [rw] error_message
|
|
@@ -51,7 +49,6 @@ module RubyLLM
|
|
|
51
49
|
include Execution::Metrics
|
|
52
50
|
include Execution::Scopes
|
|
53
51
|
include Execution::Analytics
|
|
54
|
-
include Execution::Workflow
|
|
55
52
|
|
|
56
53
|
# Status enum
|
|
57
54
|
# - running: execution in progress
|
|
@@ -72,20 +69,25 @@ module RubyLLM
|
|
|
72
69
|
has_many :child_executions, class_name: "RubyLLM::Agents::Execution",
|
|
73
70
|
foreign_key: :parent_execution_id, dependent: :nullify, inverse_of: :parent_execution
|
|
74
71
|
|
|
75
|
-
#
|
|
76
|
-
|
|
72
|
+
# Detail record for large payloads (prompts, responses, tool calls, etc.)
|
|
73
|
+
has_one :detail, class_name: "RubyLLM::Agents::ExecutionDetail",
|
|
74
|
+
foreign_key: :execution_id, dependent: :destroy
|
|
75
|
+
|
|
76
|
+
# Delegations so existing code keeps working transparently
|
|
77
|
+
delegate :system_prompt, :user_prompt, :response, :error_message,
|
|
78
|
+
:messages_summary, :tool_calls, :attempts, :fallback_chain,
|
|
79
|
+
:parameters, :routed_to, :classification_result,
|
|
80
|
+
:cached_at, :cache_creation_tokens,
|
|
81
|
+
to: :detail, prefix: false, allow_nil: true
|
|
77
82
|
|
|
78
83
|
# Validations
|
|
79
84
|
validates :agent_type, :model_id, :started_at, presence: true
|
|
80
85
|
validates :status, inclusion: { in: statuses.keys }
|
|
81
|
-
validates :agent_version, presence: true
|
|
82
86
|
validates :temperature, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 2 }, allow_nil: true
|
|
83
87
|
validates :input_tokens, :output_tokens, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
84
88
|
validates :duration_ms, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
85
89
|
validates :input_cost, :output_cost, :total_cost, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
86
90
|
validates :finish_reason, inclusion: { in: FINISH_REASONS }, allow_nil: true
|
|
87
|
-
validates :fallback_reason, inclusion: { in: FALLBACK_REASONS }, allow_nil: true
|
|
88
|
-
validates :time_to_first_token_ms, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
89
91
|
|
|
90
92
|
before_save :calculate_total_tokens, if: -> { input_tokens_changed? || output_tokens_changed? }
|
|
91
93
|
before_save :calculate_total_cost, if: -> { input_cost_changed? || output_cost_changed? }
|
|
@@ -205,7 +207,41 @@ module RubyLLM
|
|
|
205
207
|
#
|
|
206
208
|
# @return [Boolean] true if rate limiting occurred
|
|
207
209
|
def rate_limited?
|
|
208
|
-
rate_limited == true
|
|
210
|
+
metadata&.dig("rate_limited") == true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Convenience accessors for niche fields stored in metadata JSON
|
|
214
|
+
%w[span_id response_cache_key fallback_reason].each do |field|
|
|
215
|
+
define_method(field) { metadata&.dig(field) }
|
|
216
|
+
define_method(:"#{field}=") { |val| self.metadata = (metadata || {}).merge(field => val) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
%w[time_to_first_token_ms].each do |field|
|
|
220
|
+
define_method(field) { metadata&.dig(field)&.to_i }
|
|
221
|
+
define_method(:"#{field}=") { |val| self.metadata = (metadata || {}).merge(field => val) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def retryable
|
|
225
|
+
metadata&.dig("retryable")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def retryable=(val)
|
|
229
|
+
self.metadata = (metadata || {}).merge("retryable" => val)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def rate_limited
|
|
233
|
+
metadata&.dig("rate_limited")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def rate_limited=(val)
|
|
237
|
+
self.metadata = (metadata || {}).merge("rate_limited" => val)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Convenience method to access tenant_record through the tenant
|
|
241
|
+
def tenant_record
|
|
242
|
+
return nil unless tenant_id.present?
|
|
243
|
+
|
|
244
|
+
Tenant.find_by(tenant_id: tenant_id)&.tenant_record
|
|
209
245
|
end
|
|
210
246
|
|
|
211
247
|
# Returns whether this execution used streaming
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Stores large payload data for an execution (prompts, responses, tool calls, etc.)
|
|
6
|
+
#
|
|
7
|
+
# Separated from {Execution} to keep the main table lean for analytics queries.
|
|
8
|
+
# Only created when there is detail data to store.
|
|
9
|
+
#
|
|
10
|
+
# @see Execution
|
|
11
|
+
# @api public
|
|
12
|
+
class ExecutionDetail < ::ActiveRecord::Base
|
|
13
|
+
self.table_name = "ruby_llm_agents_execution_details"
|
|
14
|
+
|
|
15
|
+
belongs_to :execution, class_name: "RubyLLM::Agents::Execution"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -170,17 +170,30 @@ module RubyLLM
|
|
|
170
170
|
effective_enforcement == :soft
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
-
# Check if within budget for a specific type
|
|
173
|
+
# Check if within budget for a specific type using counter columns
|
|
174
174
|
#
|
|
175
175
|
# @param type [Symbol] :daily_cost, :monthly_cost, :daily_tokens,
|
|
176
176
|
# :monthly_tokens, :daily_executions, :monthly_executions
|
|
177
177
|
# @return [Boolean]
|
|
178
178
|
def within_budget?(type: :daily_cost)
|
|
179
|
-
|
|
180
|
-
return true unless status[:enabled]
|
|
179
|
+
return true unless budgets_enabled?
|
|
181
180
|
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
case type
|
|
182
|
+
when :daily_cost
|
|
183
|
+
within_daily_cost_budget?
|
|
184
|
+
when :monthly_cost
|
|
185
|
+
within_monthly_cost_budget?
|
|
186
|
+
when :daily_tokens
|
|
187
|
+
within_daily_token_budget?
|
|
188
|
+
when :monthly_tokens
|
|
189
|
+
within_monthly_token_budget?
|
|
190
|
+
when :daily_executions
|
|
191
|
+
within_daily_execution_budget?
|
|
192
|
+
when :monthly_executions
|
|
193
|
+
within_monthly_execution_budget?
|
|
194
|
+
else
|
|
195
|
+
true
|
|
196
|
+
end
|
|
184
197
|
end
|
|
185
198
|
|
|
186
199
|
# Get remaining budget for a specific type
|
|
@@ -188,9 +201,20 @@ module RubyLLM
|
|
|
188
201
|
# @param type [Symbol] Budget type (see #within_budget?)
|
|
189
202
|
# @return [Numeric, nil]
|
|
190
203
|
def remaining_budget(type: :daily_cost)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
case type
|
|
205
|
+
when :daily_cost
|
|
206
|
+
effective_daily_limit && (ensure_daily_reset!; effective_daily_limit - daily_cost_spent)
|
|
207
|
+
when :monthly_cost
|
|
208
|
+
effective_monthly_limit && (ensure_monthly_reset!; effective_monthly_limit - monthly_cost_spent)
|
|
209
|
+
when :daily_tokens
|
|
210
|
+
effective_daily_token_limit && (ensure_daily_reset!; effective_daily_token_limit - daily_tokens_used)
|
|
211
|
+
when :monthly_tokens
|
|
212
|
+
effective_monthly_token_limit && (ensure_monthly_reset!; effective_monthly_token_limit - monthly_tokens_used)
|
|
213
|
+
when :daily_executions
|
|
214
|
+
effective_daily_execution_limit && (ensure_daily_reset!; effective_daily_execution_limit - daily_executions_count)
|
|
215
|
+
when :monthly_executions
|
|
216
|
+
effective_monthly_execution_limit && (ensure_monthly_reset!; effective_monthly_execution_limit - monthly_executions_count)
|
|
217
|
+
end
|
|
194
218
|
end
|
|
195
219
|
|
|
196
220
|
# Check budget and raise if exceeded (for hard enforcement)
|
|
@@ -198,14 +222,98 @@ module RubyLLM
|
|
|
198
222
|
# @param agent_type [String] The agent class name
|
|
199
223
|
# @raise [BudgetExceededError] If hard enforcement and over budget
|
|
200
224
|
def check_budget!(agent_type = nil)
|
|
201
|
-
|
|
225
|
+
return unless budgets_enabled?
|
|
226
|
+
return unless hard_enforcement?
|
|
227
|
+
|
|
228
|
+
ensure_daily_reset!
|
|
229
|
+
ensure_monthly_reset!
|
|
230
|
+
|
|
231
|
+
if effective_daily_limit && daily_cost_spent >= effective_daily_limit
|
|
232
|
+
raise Reliability::BudgetExceededError.new(
|
|
233
|
+
:global_daily, effective_daily_limit, daily_cost_spent, tenant_id: tenant_id
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
if effective_monthly_limit && monthly_cost_spent >= effective_monthly_limit
|
|
238
|
+
raise Reliability::BudgetExceededError.new(
|
|
239
|
+
:global_monthly, effective_monthly_limit, monthly_cost_spent, tenant_id: tenant_id
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
if effective_daily_token_limit && daily_tokens_used >= effective_daily_token_limit
|
|
244
|
+
raise Reliability::BudgetExceededError.new(
|
|
245
|
+
:global_daily_tokens, effective_daily_token_limit, daily_tokens_used, tenant_id: tenant_id
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
if effective_monthly_token_limit && monthly_tokens_used >= effective_monthly_token_limit
|
|
250
|
+
raise Reliability::BudgetExceededError.new(
|
|
251
|
+
:global_monthly_tokens, effective_monthly_token_limit, monthly_tokens_used, tenant_id: tenant_id
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
if effective_daily_execution_limit && daily_executions_count >= effective_daily_execution_limit
|
|
256
|
+
raise Reliability::BudgetExceededError.new(
|
|
257
|
+
:global_daily_executions, effective_daily_execution_limit, daily_executions_count, tenant_id: tenant_id
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if effective_monthly_execution_limit && monthly_executions_count >= effective_monthly_execution_limit
|
|
262
|
+
raise Reliability::BudgetExceededError.new(
|
|
263
|
+
:global_monthly_executions, effective_monthly_execution_limit, monthly_executions_count, tenant_id: tenant_id
|
|
264
|
+
)
|
|
265
|
+
end
|
|
202
266
|
end
|
|
203
267
|
|
|
204
|
-
# Get full budget status
|
|
268
|
+
# Get full budget status using counter columns
|
|
205
269
|
#
|
|
206
270
|
# @return [Hash] Budget status with usage information
|
|
207
271
|
def budget_status
|
|
208
|
-
|
|
272
|
+
ensure_daily_reset!
|
|
273
|
+
ensure_monthly_reset!
|
|
274
|
+
|
|
275
|
+
{
|
|
276
|
+
enabled: budgets_enabled?,
|
|
277
|
+
enforcement: effective_enforcement,
|
|
278
|
+
global_daily: budget_status_for(effective_daily_limit, daily_cost_spent),
|
|
279
|
+
global_monthly: budget_status_for(effective_monthly_limit, monthly_cost_spent),
|
|
280
|
+
global_daily_tokens: budget_status_for(effective_daily_token_limit, daily_tokens_used),
|
|
281
|
+
global_monthly_tokens: budget_status_for(effective_monthly_token_limit, monthly_tokens_used),
|
|
282
|
+
global_daily_executions: budget_status_for(effective_daily_execution_limit, daily_executions_count),
|
|
283
|
+
global_monthly_executions: budget_status_for(effective_monthly_execution_limit, monthly_executions_count)
|
|
284
|
+
}
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Individual budget check methods
|
|
288
|
+
|
|
289
|
+
def within_daily_cost_budget?
|
|
290
|
+
ensure_daily_reset!
|
|
291
|
+
effective_daily_limit.nil? || daily_cost_spent < effective_daily_limit
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def within_monthly_cost_budget?
|
|
295
|
+
ensure_monthly_reset!
|
|
296
|
+
effective_monthly_limit.nil? || monthly_cost_spent < effective_monthly_limit
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def within_daily_token_budget?
|
|
300
|
+
ensure_daily_reset!
|
|
301
|
+
effective_daily_token_limit.nil? || daily_tokens_used < effective_daily_token_limit
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def within_monthly_token_budget?
|
|
305
|
+
ensure_monthly_reset!
|
|
306
|
+
effective_monthly_token_limit.nil? || monthly_tokens_used < effective_monthly_token_limit
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def within_daily_execution_budget?
|
|
310
|
+
ensure_daily_reset!
|
|
311
|
+
effective_daily_execution_limit.nil? || daily_executions_count < effective_daily_execution_limit
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def within_monthly_execution_budget?
|
|
315
|
+
ensure_monthly_reset!
|
|
316
|
+
effective_monthly_execution_limit.nil? || monthly_executions_count < effective_monthly_execution_limit
|
|
209
317
|
end
|
|
210
318
|
|
|
211
319
|
# Convert to config hash for BudgetTracker
|
|
@@ -256,20 +364,20 @@ module RubyLLM
|
|
|
256
364
|
(global_config&.dig(:per_agent_monthly) || {}).merge(per_agent_monthly || {})
|
|
257
365
|
end
|
|
258
366
|
|
|
259
|
-
#
|
|
367
|
+
# Builds a status hash for a single budget dimension
|
|
260
368
|
#
|
|
261
|
-
# @param
|
|
262
|
-
# @
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
369
|
+
# @param limit [Numeric, nil] The configured limit
|
|
370
|
+
# @param current [Numeric] The current usage
|
|
371
|
+
# @return [Hash, nil]
|
|
372
|
+
def budget_status_for(limit, current)
|
|
373
|
+
return nil unless limit
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
limit: limit,
|
|
377
|
+
current_spend: current,
|
|
378
|
+
remaining: [limit - current, 0].max,
|
|
379
|
+
percentage_used: (current.to_f / limit * 100).round(1)
|
|
380
|
+
}
|
|
273
381
|
end
|
|
274
382
|
end
|
|
275
383
|
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class Tenant
|
|
6
|
+
# Provides atomic SQL increment of usage counters after each execution.
|
|
7
|
+
#
|
|
8
|
+
# @example Recording an execution
|
|
9
|
+
# tenant.record_execution!(cost: 0.05, tokens: 1200)
|
|
10
|
+
# tenant.record_execution!(cost: 0.01, tokens: 500, error: true)
|
|
11
|
+
#
|
|
12
|
+
# @api public
|
|
13
|
+
module Incrementable
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
# Records an execution by atomically incrementing all counter columns.
|
|
17
|
+
#
|
|
18
|
+
# @param cost [Numeric] The cost of the execution in USD
|
|
19
|
+
# @param tokens [Integer] The number of tokens used
|
|
20
|
+
# @param error [Boolean] Whether the execution was an error
|
|
21
|
+
# @return [void]
|
|
22
|
+
def record_execution!(cost:, tokens:, error: false)
|
|
23
|
+
ensure_daily_reset!
|
|
24
|
+
ensure_monthly_reset!
|
|
25
|
+
|
|
26
|
+
error_inc = error ? 1 : 0
|
|
27
|
+
status = error ? "error" : "success"
|
|
28
|
+
|
|
29
|
+
self.class.where(id: id).update_all(
|
|
30
|
+
self.class.sanitize_sql_array([
|
|
31
|
+
<<~SQL,
|
|
32
|
+
daily_cost_spent = daily_cost_spent + ?,
|
|
33
|
+
monthly_cost_spent = monthly_cost_spent + ?,
|
|
34
|
+
daily_tokens_used = daily_tokens_used + ?,
|
|
35
|
+
monthly_tokens_used = monthly_tokens_used + ?,
|
|
36
|
+
daily_executions_count = daily_executions_count + 1,
|
|
37
|
+
monthly_executions_count = monthly_executions_count + 1,
|
|
38
|
+
daily_error_count = daily_error_count + ?,
|
|
39
|
+
monthly_error_count = monthly_error_count + ?,
|
|
40
|
+
last_execution_at = ?,
|
|
41
|
+
last_execution_status = ?
|
|
42
|
+
SQL
|
|
43
|
+
cost.to_f, cost.to_f,
|
|
44
|
+
tokens.to_i, tokens.to_i,
|
|
45
|
+
error_inc, error_inc,
|
|
46
|
+
Time.current, status
|
|
47
|
+
])
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
reload
|
|
51
|
+
check_soft_cap_alerts!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Checks soft cap alerts after recording an execution.
|
|
57
|
+
#
|
|
58
|
+
# @return [void]
|
|
59
|
+
def check_soft_cap_alerts!
|
|
60
|
+
return unless soft_enforcement?
|
|
61
|
+
|
|
62
|
+
check_cost_alerts!
|
|
63
|
+
check_token_alerts!
|
|
64
|
+
check_execution_alerts!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [void]
|
|
68
|
+
def check_cost_alerts!
|
|
69
|
+
if effective_daily_limit && daily_cost_spent >= effective_daily_limit
|
|
70
|
+
AlertManager.notify(:budget_soft_cap, {
|
|
71
|
+
tenant_id: tenant_id, type: :daily_cost,
|
|
72
|
+
limit: effective_daily_limit, total: daily_cost_spent
|
|
73
|
+
})
|
|
74
|
+
end
|
|
75
|
+
if effective_monthly_limit && monthly_cost_spent >= effective_monthly_limit
|
|
76
|
+
AlertManager.notify(:budget_soft_cap, {
|
|
77
|
+
tenant_id: tenant_id, type: :monthly_cost,
|
|
78
|
+
limit: effective_monthly_limit, total: monthly_cost_spent
|
|
79
|
+
})
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [void]
|
|
84
|
+
def check_token_alerts!
|
|
85
|
+
if effective_daily_token_limit && daily_tokens_used >= effective_daily_token_limit
|
|
86
|
+
AlertManager.notify(:token_soft_cap, {
|
|
87
|
+
tenant_id: tenant_id, type: :daily_tokens,
|
|
88
|
+
limit: effective_daily_token_limit, total: daily_tokens_used
|
|
89
|
+
})
|
|
90
|
+
end
|
|
91
|
+
if effective_monthly_token_limit && monthly_tokens_used >= effective_monthly_token_limit
|
|
92
|
+
AlertManager.notify(:token_soft_cap, {
|
|
93
|
+
tenant_id: tenant_id, type: :monthly_tokens,
|
|
94
|
+
limit: effective_monthly_token_limit, total: monthly_tokens_used
|
|
95
|
+
})
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [void]
|
|
100
|
+
def check_execution_alerts!
|
|
101
|
+
if effective_daily_execution_limit && daily_executions_count >= effective_daily_execution_limit
|
|
102
|
+
AlertManager.notify(:budget_soft_cap, {
|
|
103
|
+
tenant_id: tenant_id, type: :daily_executions,
|
|
104
|
+
limit: effective_daily_execution_limit, total: daily_executions_count
|
|
105
|
+
})
|
|
106
|
+
end
|
|
107
|
+
if effective_monthly_execution_limit && monthly_executions_count >= effective_monthly_execution_limit
|
|
108
|
+
AlertManager.notify(:budget_soft_cap, {
|
|
109
|
+
tenant_id: tenant_id, type: :monthly_executions,
|
|
110
|
+
limit: effective_monthly_execution_limit, total: monthly_executions_count
|
|
111
|
+
})
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|