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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -334
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +46 -10
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +52 -6
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +58 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
  99. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  100. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  101. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  102. data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
  103. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  104. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  105. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  106. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  107. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  108. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  109. data/lib/ruby_llm/agents/results/base.rb +1 -49
  110. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  111. data/lib/ruby_llm/agents.rb +1 -9
  112. data/lib/tasks/ruby_llm_agents.rake +34 -0
  113. metadata +12 -81
  114. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  115. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  116. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  117. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  118. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  119. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  120. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  121. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  122. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  123. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  125. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  126. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  127. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  128. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  129. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  130. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  131. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  132. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  133. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  134. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  135. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  136. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  137. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  138. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  139. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  140. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  141. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  142. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  143. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  144. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  145. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  146. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  147. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  148. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  149. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  150. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  151. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  152. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  153. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  154. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  155. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  156. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  157. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  158. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  159. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  160. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  161. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  162. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  163. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  164. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  165. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  166. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  167. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  168. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  169. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  170. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  171. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  172. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  173. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  174. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  175. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  176. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  177. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  178. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  180. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  181. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  182. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  183. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  185. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  186. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  187. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  188. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  189. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  190. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  191. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  192. 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
- cost(period: :today)
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
- cost(period: :this_month)
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
- tokens(period: :today)
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
- tokens(period: :this_month)
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
- execution_count(period: :today)
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
- execution_count(period: :this_month)
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::Configurable
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 and workflow classes are loaded
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
- all_agents = (agents + workflows + embedders + moderators + speakers + transcribers + image_generators).uniq
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 and workflow files to register descendants
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, workflow, embedder, moderator, speaker, transcriber)
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) || (is_workflow ? "workflow" : "N/A"),
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", "workflow", "embedder", "moderator", "speaker", "transcriber", or "image_generator"
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