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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class Tenant
|
|
6
|
+
# Handles lazy reset of usage counters when periods roll over,
|
|
7
|
+
# and provides refresh_counters! for reconciliation from the executions table.
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
module Resettable
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
# Resets daily counters if the day has rolled over.
|
|
14
|
+
# Uses a WHERE guard to prevent race conditions with concurrent requests.
|
|
15
|
+
#
|
|
16
|
+
# @return [void]
|
|
17
|
+
def ensure_daily_reset!
|
|
18
|
+
return if daily_reset_date == Date.current
|
|
19
|
+
|
|
20
|
+
rows = self.class.where(id: id)
|
|
21
|
+
.where("daily_reset_date IS NULL OR daily_reset_date < ?", Date.current)
|
|
22
|
+
.update_all(
|
|
23
|
+
daily_cost_spent: 0,
|
|
24
|
+
daily_tokens_used: 0,
|
|
25
|
+
daily_executions_count: 0,
|
|
26
|
+
daily_error_count: 0,
|
|
27
|
+
daily_reset_date: Date.current
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
reload if rows > 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Resets monthly counters if the month has rolled over.
|
|
34
|
+
# Uses a WHERE guard to prevent race conditions with concurrent requests.
|
|
35
|
+
#
|
|
36
|
+
# @return [void]
|
|
37
|
+
def ensure_monthly_reset!
|
|
38
|
+
bom = Date.current.beginning_of_month
|
|
39
|
+
return if monthly_reset_date == bom
|
|
40
|
+
|
|
41
|
+
rows = self.class.where(id: id)
|
|
42
|
+
.where("monthly_reset_date IS NULL OR monthly_reset_date < ?", bom)
|
|
43
|
+
.update_all(
|
|
44
|
+
monthly_cost_spent: 0,
|
|
45
|
+
monthly_tokens_used: 0,
|
|
46
|
+
monthly_executions_count: 0,
|
|
47
|
+
monthly_error_count: 0,
|
|
48
|
+
monthly_reset_date: bom
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
reload if rows > 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Recalculates all counters from the source-of-truth executions table.
|
|
55
|
+
#
|
|
56
|
+
# Use when counters have drifted due to manual DB edits, failed writes,
|
|
57
|
+
# or after deleting/updating execution records.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def refresh_counters!
|
|
61
|
+
today = Date.current
|
|
62
|
+
bom = today.beginning_of_month
|
|
63
|
+
|
|
64
|
+
daily_stats = aggregate_stats(
|
|
65
|
+
executions.where("created_at >= ?", today.beginning_of_day)
|
|
66
|
+
)
|
|
67
|
+
monthly_stats = aggregate_stats(
|
|
68
|
+
executions.where("created_at >= ?", bom.beginning_of_day)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
last_exec = executions.order(created_at: :desc).pick(:created_at, :status)
|
|
72
|
+
|
|
73
|
+
update_columns(
|
|
74
|
+
daily_cost_spent: daily_stats[:cost],
|
|
75
|
+
daily_tokens_used: daily_stats[:tokens],
|
|
76
|
+
daily_executions_count: daily_stats[:count],
|
|
77
|
+
daily_error_count: daily_stats[:errors],
|
|
78
|
+
daily_reset_date: today,
|
|
79
|
+
|
|
80
|
+
monthly_cost_spent: monthly_stats[:cost],
|
|
81
|
+
monthly_tokens_used: monthly_stats[:tokens],
|
|
82
|
+
monthly_executions_count: monthly_stats[:count],
|
|
83
|
+
monthly_error_count: monthly_stats[:errors],
|
|
84
|
+
monthly_reset_date: bom,
|
|
85
|
+
|
|
86
|
+
last_execution_at: last_exec&.first,
|
|
87
|
+
last_execution_status: last_exec&.last
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
reload
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class_methods do
|
|
94
|
+
# Refresh counters for all tenants
|
|
95
|
+
#
|
|
96
|
+
# @return [void]
|
|
97
|
+
def refresh_all_counters!
|
|
98
|
+
find_each(&:refresh_counters!)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Refresh counters for active tenants only
|
|
102
|
+
#
|
|
103
|
+
# @return [void]
|
|
104
|
+
def refresh_active_counters!
|
|
105
|
+
active.find_each(&:refresh_counters!)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Aggregates cost, tokens, count, and errors from an executions scope.
|
|
112
|
+
#
|
|
113
|
+
# @param scope [ActiveRecord::Relation]
|
|
114
|
+
# @return [Hash] { cost:, tokens:, count:, errors: }
|
|
115
|
+
def aggregate_stats(scope)
|
|
116
|
+
agg = scope.pick(
|
|
117
|
+
Arel.sql("COALESCE(SUM(total_cost), 0)"),
|
|
118
|
+
Arel.sql("COALESCE(SUM(total_tokens), 0)"),
|
|
119
|
+
Arel.sql("COUNT(*)"),
|
|
120
|
+
Arel.sql("COALESCE(SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END), 0)")
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
{ cost: agg[0], tokens: agg[1], count: agg[2], errors: agg[3] }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -50,11 +50,12 @@ module RubyLLM
|
|
|
50
50
|
scope.sum(:total_cost) || 0
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# Returns today's cost
|
|
53
|
+
# Returns today's cost from counter columns
|
|
54
54
|
#
|
|
55
55
|
# @return [Float]
|
|
56
56
|
def cost_today
|
|
57
|
-
|
|
57
|
+
ensure_daily_reset!
|
|
58
|
+
daily_cost_spent
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
# Returns yesterday's cost
|
|
@@ -78,11 +79,12 @@ module RubyLLM
|
|
|
78
79
|
cost(period: :last_week)
|
|
79
80
|
end
|
|
80
81
|
|
|
81
|
-
# Returns this month's cost
|
|
82
|
+
# Returns this month's cost from counter columns
|
|
82
83
|
#
|
|
83
84
|
# @return [Float]
|
|
84
85
|
def cost_this_month
|
|
85
|
-
|
|
86
|
+
ensure_monthly_reset!
|
|
87
|
+
monthly_cost_spent
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
# Returns last month's cost
|
|
@@ -104,11 +106,12 @@ module RubyLLM
|
|
|
104
106
|
scope.sum(:total_tokens) || 0
|
|
105
107
|
end
|
|
106
108
|
|
|
107
|
-
# Returns today's token usage
|
|
109
|
+
# Returns today's token usage from counter columns
|
|
108
110
|
#
|
|
109
111
|
# @return [Integer]
|
|
110
112
|
def tokens_today
|
|
111
|
-
|
|
113
|
+
ensure_daily_reset!
|
|
114
|
+
daily_tokens_used
|
|
112
115
|
end
|
|
113
116
|
|
|
114
117
|
# Returns yesterday's token usage
|
|
@@ -125,11 +128,12 @@ module RubyLLM
|
|
|
125
128
|
tokens(period: :this_week)
|
|
126
129
|
end
|
|
127
130
|
|
|
128
|
-
# Returns this month's token usage
|
|
131
|
+
# Returns this month's token usage from counter columns
|
|
129
132
|
#
|
|
130
133
|
# @return [Integer]
|
|
131
134
|
def tokens_this_month
|
|
132
|
-
|
|
135
|
+
ensure_monthly_reset!
|
|
136
|
+
monthly_tokens_used
|
|
133
137
|
end
|
|
134
138
|
|
|
135
139
|
# Returns last month's token usage
|
|
@@ -151,11 +155,12 @@ module RubyLLM
|
|
|
151
155
|
scope.count
|
|
152
156
|
end
|
|
153
157
|
|
|
154
|
-
# Returns today's execution count
|
|
158
|
+
# Returns today's execution count from counter columns
|
|
155
159
|
#
|
|
156
160
|
# @return [Integer]
|
|
157
161
|
def executions_today
|
|
158
|
-
|
|
162
|
+
ensure_daily_reset!
|
|
163
|
+
daily_executions_count
|
|
159
164
|
end
|
|
160
165
|
|
|
161
166
|
# Returns yesterday's execution count
|
|
@@ -172,11 +177,12 @@ module RubyLLM
|
|
|
172
177
|
execution_count(period: :this_week)
|
|
173
178
|
end
|
|
174
179
|
|
|
175
|
-
# Returns this month's execution count
|
|
180
|
+
# Returns this month's execution count from counter columns
|
|
176
181
|
#
|
|
177
182
|
# @return [Integer]
|
|
178
183
|
def executions_this_month
|
|
179
|
-
|
|
184
|
+
ensure_monthly_reset!
|
|
185
|
+
monthly_executions_count
|
|
180
186
|
end
|
|
181
187
|
|
|
182
188
|
# Returns last month's execution count
|
|
@@ -186,6 +192,34 @@ module RubyLLM
|
|
|
186
192
|
execution_count(period: :last_month)
|
|
187
193
|
end
|
|
188
194
|
|
|
195
|
+
# Error count queries
|
|
196
|
+
|
|
197
|
+
# Returns today's error count from counter columns
|
|
198
|
+
#
|
|
199
|
+
# @return [Integer]
|
|
200
|
+
def errors_today
|
|
201
|
+
ensure_daily_reset!
|
|
202
|
+
daily_error_count
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns this month's error count from counter columns
|
|
206
|
+
#
|
|
207
|
+
# @return [Integer]
|
|
208
|
+
def errors_this_month
|
|
209
|
+
ensure_monthly_reset!
|
|
210
|
+
monthly_error_count
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns today's success rate from counter columns
|
|
214
|
+
#
|
|
215
|
+
# @return [Float] Percentage (0.0-100.0)
|
|
216
|
+
def success_rate_today
|
|
217
|
+
ensure_daily_reset!
|
|
218
|
+
return 100.0 if daily_executions_count.zero?
|
|
219
|
+
|
|
220
|
+
((daily_executions_count - daily_error_count).to_f / daily_executions_count * 100).round(1)
|
|
221
|
+
end
|
|
222
|
+
|
|
189
223
|
# Usage summaries
|
|
190
224
|
|
|
191
225
|
# Returns a complete usage summary for the tenant
|
|
@@ -7,7 +7,6 @@ module RubyLLM
|
|
|
7
7
|
# Encapsulates all tenant-related functionality:
|
|
8
8
|
# - Budget limits and enforcement (via Budgetable concern)
|
|
9
9
|
# - Usage tracking: cost, tokens, executions (via Trackable concern)
|
|
10
|
-
# - API configuration per tenant (via Configurable concern)
|
|
11
10
|
#
|
|
12
11
|
# @example Creating a tenant
|
|
13
12
|
# Tenant.create!(
|
|
@@ -30,7 +29,6 @@ module RubyLLM
|
|
|
30
29
|
#
|
|
31
30
|
# @see Tenant::Budgetable
|
|
32
31
|
# @see Tenant::Trackable
|
|
33
|
-
# @see Tenant::Configurable
|
|
34
32
|
# @see LLMTenant
|
|
35
33
|
# @api public
|
|
36
34
|
class Tenant < ::ActiveRecord::Base
|
|
@@ -39,7 +37,8 @@ module RubyLLM
|
|
|
39
37
|
# Include concerns for organized functionality
|
|
40
38
|
include Tenant::Budgetable
|
|
41
39
|
include Tenant::Trackable
|
|
42
|
-
include Tenant::
|
|
40
|
+
include Tenant::Resettable
|
|
41
|
+
include Tenant::Incrementable
|
|
43
42
|
|
|
44
43
|
# Polymorphic association to user's tenant model (optional)
|
|
45
44
|
# Allows linking to Organization, Account, or any ActiveRecord model
|
|
@@ -8,15 +8,18 @@ module RubyLLM
|
|
|
8
8
|
# All functionality has been moved to the Tenant model with organized concerns.
|
|
9
9
|
#
|
|
10
10
|
# @example Migration path
|
|
11
|
-
# # Old usage (still works)
|
|
11
|
+
# # Old usage (still works but emits deprecation warning)
|
|
12
12
|
# TenantBudget.for_tenant("acme_corp")
|
|
13
|
-
# TenantBudget.create!(tenant_id: "acme", daily_limit: 100)
|
|
14
13
|
#
|
|
15
14
|
# # New usage (preferred)
|
|
16
15
|
# Tenant.for("acme_corp")
|
|
17
|
-
# Tenant.create!(tenant_id: "acme", daily_limit: 100)
|
|
18
16
|
#
|
|
19
17
|
# @see Tenant
|
|
20
18
|
TenantBudget = Tenant
|
|
19
|
+
|
|
20
|
+
ActiveSupport.deprecator.warn(
|
|
21
|
+
"RubyLLM::Agents::TenantBudget is deprecated. Use RubyLLM::Agents::Tenant instead. " \
|
|
22
|
+
"This alias will be removed in the next major version."
|
|
23
|
+
)
|
|
21
24
|
end
|
|
22
25
|
end
|
|
@@ -21,12 +21,6 @@ module RubyLLM
|
|
|
21
21
|
#
|
|
22
22
|
# @api public
|
|
23
23
|
class AgentRegistry
|
|
24
|
-
# Base workflow classes to exclude from listings
|
|
25
|
-
# These are abstract parent classes, not concrete workflows
|
|
26
|
-
BASE_WORKFLOW_CLASSES = [
|
|
27
|
-
"RubyLLM::Agents::Workflow"
|
|
28
|
-
].freeze
|
|
29
|
-
|
|
30
24
|
class << self
|
|
31
25
|
# Returns all unique agent type names
|
|
32
26
|
#
|
|
@@ -66,22 +60,17 @@ module RubyLLM
|
|
|
66
60
|
#
|
|
67
61
|
# @return [Array<String>] Agent class names
|
|
68
62
|
def file_system_agents
|
|
69
|
-
# Ensure all agent
|
|
63
|
+
# Ensure all agent classes are loaded
|
|
70
64
|
eager_load_agents!
|
|
71
65
|
|
|
72
66
|
# Find all descendants of all base classes
|
|
73
67
|
agents = RubyLLM::Agents::Base.descendants.map(&:name).compact
|
|
74
|
-
workflows = RubyLLM::Agents::Workflow.descendants.map(&:name).compact
|
|
75
68
|
embedders = RubyLLM::Agents::Embedder.descendants.map(&:name).compact
|
|
76
|
-
moderators = RubyLLM::Agents::Moderator.descendants.map(&:name).compact
|
|
77
69
|
speakers = RubyLLM::Agents::Speaker.descendants.map(&:name).compact
|
|
78
70
|
transcribers = RubyLLM::Agents::Transcriber.descendants.map(&:name).compact
|
|
79
71
|
image_generators = RubyLLM::Agents::ImageGenerator.descendants.map(&:name).compact
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Filter out base workflow classes
|
|
84
|
-
all_agents.reject { |name| BASE_WORKFLOW_CLASSES.include?(name) }
|
|
73
|
+
(agents + embedders + speakers + transcribers + image_generators).uniq
|
|
85
74
|
rescue StandardError => e
|
|
86
75
|
Rails.logger.error("[RubyLLM::Agents] Error loading agents from file system: #{e.message}")
|
|
87
76
|
[]
|
|
@@ -97,7 +86,7 @@ module RubyLLM
|
|
|
97
86
|
[]
|
|
98
87
|
end
|
|
99
88
|
|
|
100
|
-
# Eager loads all agent
|
|
89
|
+
# Eager loads all agent files to register descendants
|
|
101
90
|
#
|
|
102
91
|
# Uses the configured autoload paths from RubyLLM::Agents.configuration
|
|
103
92
|
# to ensure agents are discovered in the correct directories.
|
|
@@ -116,28 +105,6 @@ module RubyLLM
|
|
|
116
105
|
end
|
|
117
106
|
end
|
|
118
107
|
|
|
119
|
-
# Returns only regular agents (non-workflows)
|
|
120
|
-
#
|
|
121
|
-
# @return [Array<Hash>] Agent info hashes for non-workflow agents
|
|
122
|
-
def agents_only
|
|
123
|
-
all_with_details.reject { |a| a[:is_workflow] }
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Returns only workflows
|
|
127
|
-
#
|
|
128
|
-
# @return [Array<Hash>] Agent info hashes for workflows only
|
|
129
|
-
def workflows_only
|
|
130
|
-
all_with_details.select { |a| a[:is_workflow] }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Returns workflows filtered by type
|
|
134
|
-
#
|
|
135
|
-
# @param type [String, Symbol] The workflow type (pipeline, parallel, router)
|
|
136
|
-
# @return [Array<Hash>] Filtered workflow info hashes
|
|
137
|
-
def workflows_by_type(type)
|
|
138
|
-
workflows_only.select { |w| w[:workflow_type] == type.to_s }
|
|
139
|
-
end
|
|
140
|
-
|
|
141
108
|
# Builds detailed info hash for an agent
|
|
142
109
|
#
|
|
143
110
|
# @param agent_type [String] The agent class name
|
|
@@ -146,27 +113,17 @@ module RubyLLM
|
|
|
146
113
|
agent_class = find(agent_type)
|
|
147
114
|
stats = fetch_stats(agent_type)
|
|
148
115
|
|
|
149
|
-
# Detect the agent type (agent,
|
|
116
|
+
# Detect the agent type (agent, embedder, speaker, transcriber, image_generator)
|
|
150
117
|
detected_type = detect_agent_type(agent_class)
|
|
151
118
|
|
|
152
|
-
# Check if this is a workflow class vs a regular agent
|
|
153
|
-
is_workflow = detected_type == "workflow"
|
|
154
|
-
|
|
155
|
-
# Determine specific workflow type and children
|
|
156
|
-
workflow_type = is_workflow ? detect_workflow_type(agent_class) : nil
|
|
157
|
-
workflow_children = is_workflow ? extract_workflow_children(agent_class) : []
|
|
158
|
-
|
|
159
119
|
{
|
|
160
120
|
name: agent_type,
|
|
161
121
|
class: agent_class,
|
|
162
122
|
active: agent_class.present?,
|
|
163
123
|
agent_type: detected_type,
|
|
164
|
-
is_workflow: is_workflow,
|
|
165
|
-
workflow_type: workflow_type,
|
|
166
|
-
workflow_children: workflow_children,
|
|
167
124
|
version: safe_call(agent_class, :version) || "N/A",
|
|
168
125
|
description: safe_call(agent_class, :description),
|
|
169
|
-
model: safe_call(agent_class, :model) ||
|
|
126
|
+
model: safe_call(agent_class, :model) || "N/A",
|
|
170
127
|
temperature: safe_call(agent_class, :temperature),
|
|
171
128
|
timeout: safe_call(agent_class, :timeout),
|
|
172
129
|
cache_enabled: safe_call(agent_class, :cache_enabled?) || false,
|
|
@@ -216,22 +173,10 @@ module RubyLLM
|
|
|
216
173
|
nil
|
|
217
174
|
end
|
|
218
175
|
|
|
219
|
-
# Detects the specific workflow type from class hierarchy
|
|
220
|
-
#
|
|
221
|
-
# @param agent_class [Class, nil] The agent class
|
|
222
|
-
# @return [String, nil] "workflow" for DSL workflows, or nil
|
|
223
|
-
def detect_workflow_type(agent_class)
|
|
224
|
-
return nil unless agent_class
|
|
225
|
-
|
|
226
|
-
if agent_class.respond_to?(:step_configs) && agent_class.step_configs.any?
|
|
227
|
-
"workflow"
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
176
|
# Detects the agent type from class hierarchy
|
|
232
177
|
#
|
|
233
178
|
# @param agent_class [Class, nil] The agent class
|
|
234
|
-
# @return [String] "agent", "
|
|
179
|
+
# @return [String] "agent", "embedder", "speaker", "transcriber", or "image_generator"
|
|
235
180
|
def detect_agent_type(agent_class)
|
|
236
181
|
return "agent" unless agent_class
|
|
237
182
|
|
|
@@ -239,67 +184,16 @@ module RubyLLM
|
|
|
239
184
|
|
|
240
185
|
if ancestors.include?("RubyLLM::Agents::Embedder")
|
|
241
186
|
"embedder"
|
|
242
|
-
elsif ancestors.include?("RubyLLM::Agents::Moderator")
|
|
243
|
-
"moderator"
|
|
244
187
|
elsif ancestors.include?("RubyLLM::Agents::Speaker")
|
|
245
188
|
"speaker"
|
|
246
189
|
elsif ancestors.include?("RubyLLM::Agents::Transcriber")
|
|
247
190
|
"transcriber"
|
|
248
191
|
elsif ancestors.include?("RubyLLM::Agents::ImageGenerator")
|
|
249
192
|
"image_generator"
|
|
250
|
-
elsif ancestors.include?("RubyLLM::Agents::Workflow")
|
|
251
|
-
"workflow"
|
|
252
193
|
else
|
|
253
194
|
"agent"
|
|
254
195
|
end
|
|
255
196
|
end
|
|
256
|
-
|
|
257
|
-
# Extracts child agents from workflow DSL configuration
|
|
258
|
-
#
|
|
259
|
-
# @param agent_class [Class, nil] The workflow class
|
|
260
|
-
# @return [Array<Hash>] Array of child info hashes with :name, :agent, :type, :optional keys
|
|
261
|
-
def extract_workflow_children(agent_class)
|
|
262
|
-
return [] unless agent_class
|
|
263
|
-
|
|
264
|
-
children = []
|
|
265
|
-
|
|
266
|
-
if agent_class.respond_to?(:steps) && agent_class.steps.any?
|
|
267
|
-
# Pipeline workflow - extract steps
|
|
268
|
-
agent_class.steps.each do |name, config|
|
|
269
|
-
children << {
|
|
270
|
-
name: name,
|
|
271
|
-
agent: config[:agent]&.name,
|
|
272
|
-
type: "step",
|
|
273
|
-
optional: config[:continue_on_error] || false
|
|
274
|
-
}
|
|
275
|
-
end
|
|
276
|
-
elsif agent_class.respond_to?(:branches) && agent_class.branches.any?
|
|
277
|
-
# Parallel workflow - extract branches
|
|
278
|
-
agent_class.branches.each do |name, config|
|
|
279
|
-
children << {
|
|
280
|
-
name: name,
|
|
281
|
-
agent: config[:agent]&.name,
|
|
282
|
-
type: "branch",
|
|
283
|
-
optional: config[:optional] || false
|
|
284
|
-
}
|
|
285
|
-
end
|
|
286
|
-
elsif agent_class.respond_to?(:routes) && agent_class.routes.any?
|
|
287
|
-
# Router workflow - extract routes
|
|
288
|
-
agent_class.routes.each do |name, config|
|
|
289
|
-
children << {
|
|
290
|
-
name: name,
|
|
291
|
-
agent: config[:agent]&.name,
|
|
292
|
-
type: "route",
|
|
293
|
-
description: config[:description]
|
|
294
|
-
}
|
|
295
|
-
end
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
children
|
|
299
|
-
rescue StandardError => e
|
|
300
|
-
Rails.logger.error("[RubyLLM::Agents] Error extracting workflow children: #{e.message}")
|
|
301
|
-
[]
|
|
302
|
-
end
|
|
303
197
|
end
|
|
304
198
|
end
|
|
305
199
|
end
|