ruby_llm-agents 1.0.0 → 1.2.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/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
- data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
- data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
- data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
- data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
- data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
- data/app/models/ruby_llm/agents/execution.rb +50 -14
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
- data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
- data/app/models/ruby_llm/agents/tenant.rb +146 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
- data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
- data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
- data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
- data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
- data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
- data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
- data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
- data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
- data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
- data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
- data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
- data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
- data/config/routes.rb +1 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
- data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
- data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
- data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
- data/lib/ruby_llm/agents/core/configuration.rb +55 -43
- data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
- data/lib/ruby_llm/agents/pipeline.rb +69 -0
- data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
- data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
- data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
- data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
- data/lib/ruby_llm/agents/workflow/result.rb +202 -0
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
- data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
- metadata +43 -6
- data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
- data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
- data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
- data/lib/ruby_llm/agents/workflow/router.rb +0 -429
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class Tenant
|
|
6
|
+
# Handles budget limits and enforcement for tenants.
|
|
7
|
+
#
|
|
8
|
+
# Supports three types of limits:
|
|
9
|
+
# - Cost limits (USD): daily_limit, monthly_limit
|
|
10
|
+
# - Token limits: daily_token_limit, monthly_token_limit
|
|
11
|
+
# - Execution limits: daily_execution_limit, monthly_execution_limit
|
|
12
|
+
#
|
|
13
|
+
# Enforcement modes:
|
|
14
|
+
# - :none - No enforcement, tracking only
|
|
15
|
+
# - :soft - Log warnings when limits exceeded
|
|
16
|
+
# - :hard - Block execution when limits exceeded
|
|
17
|
+
#
|
|
18
|
+
# @example Setting limits
|
|
19
|
+
# tenant.daily_limit = 100.0
|
|
20
|
+
# tenant.monthly_limit = 1000.0
|
|
21
|
+
# tenant.enforcement = "hard"
|
|
22
|
+
#
|
|
23
|
+
# @example Checking budget
|
|
24
|
+
# tenant.within_budget? # => true
|
|
25
|
+
# tenant.within_budget?(type: :monthly_cost)
|
|
26
|
+
# tenant.remaining_budget(type: :daily_tokens)
|
|
27
|
+
#
|
|
28
|
+
# @see BudgetTracker
|
|
29
|
+
# @api public
|
|
30
|
+
module Budgetable
|
|
31
|
+
extend ActiveSupport::Concern
|
|
32
|
+
|
|
33
|
+
# Valid enforcement modes
|
|
34
|
+
ENFORCEMENT_MODES = %w[none soft hard].freeze
|
|
35
|
+
|
|
36
|
+
included do
|
|
37
|
+
# Validations
|
|
38
|
+
validates :enforcement, inclusion: { in: ENFORCEMENT_MODES }, allow_nil: true
|
|
39
|
+
validates :daily_limit, :monthly_limit,
|
|
40
|
+
numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
41
|
+
validates :daily_token_limit, :monthly_token_limit,
|
|
42
|
+
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
43
|
+
validates :daily_execution_limit, :monthly_execution_limit,
|
|
44
|
+
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
45
|
+
|
|
46
|
+
# Scopes
|
|
47
|
+
scope :with_budgets, -> { where.not(daily_limit: nil).or(where.not(monthly_limit: nil)) }
|
|
48
|
+
scope :with_enforcement, ->(mode) { where(enforcement: mode) }
|
|
49
|
+
scope :hard_enforcement, -> { where(enforcement: "hard") }
|
|
50
|
+
scope :soft_enforcement, -> { where(enforcement: "soft") }
|
|
51
|
+
scope :no_enforcement, -> { where(enforcement: "none") }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Effective limits (considering inheritance from global config)
|
|
55
|
+
|
|
56
|
+
# Returns the effective daily cost limit
|
|
57
|
+
#
|
|
58
|
+
# @return [Float, nil] The daily limit or nil if not set
|
|
59
|
+
def effective_daily_limit
|
|
60
|
+
return daily_limit if daily_limit.present?
|
|
61
|
+
return nil unless inherit_global_defaults
|
|
62
|
+
|
|
63
|
+
global_config&.dig(:global_daily)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns the effective monthly cost limit
|
|
67
|
+
#
|
|
68
|
+
# @return [Float, nil] The monthly limit or nil if not set
|
|
69
|
+
def effective_monthly_limit
|
|
70
|
+
return monthly_limit if monthly_limit.present?
|
|
71
|
+
return nil unless inherit_global_defaults
|
|
72
|
+
|
|
73
|
+
global_config&.dig(:global_monthly)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns the effective daily token limit
|
|
77
|
+
#
|
|
78
|
+
# @return [Integer, nil] The daily token limit or nil if not set
|
|
79
|
+
def effective_daily_token_limit
|
|
80
|
+
return daily_token_limit if daily_token_limit.present?
|
|
81
|
+
return nil unless inherit_global_defaults
|
|
82
|
+
|
|
83
|
+
global_config&.dig(:global_daily_tokens)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the effective monthly token limit
|
|
87
|
+
#
|
|
88
|
+
# @return [Integer, nil] The monthly token limit or nil if not set
|
|
89
|
+
def effective_monthly_token_limit
|
|
90
|
+
return monthly_token_limit if monthly_token_limit.present?
|
|
91
|
+
return nil unless inherit_global_defaults
|
|
92
|
+
|
|
93
|
+
global_config&.dig(:global_monthly_tokens)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the effective daily execution limit
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer, nil] The daily execution limit or nil if not set
|
|
99
|
+
def effective_daily_execution_limit
|
|
100
|
+
return daily_execution_limit if daily_execution_limit.present?
|
|
101
|
+
return nil unless inherit_global_defaults
|
|
102
|
+
|
|
103
|
+
global_config&.dig(:global_daily_executions)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the effective monthly execution limit
|
|
107
|
+
#
|
|
108
|
+
# @return [Integer, nil] The monthly execution limit or nil if not set
|
|
109
|
+
def effective_monthly_execution_limit
|
|
110
|
+
return monthly_execution_limit if monthly_execution_limit.present?
|
|
111
|
+
return nil unless inherit_global_defaults
|
|
112
|
+
|
|
113
|
+
global_config&.dig(:global_monthly_executions)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns the effective enforcement mode
|
|
117
|
+
#
|
|
118
|
+
# @return [Symbol] :none, :soft, or :hard
|
|
119
|
+
def effective_enforcement
|
|
120
|
+
return enforcement.to_sym if enforcement.present?
|
|
121
|
+
return :soft unless inherit_global_defaults
|
|
122
|
+
|
|
123
|
+
RubyLLM::Agents.configuration.budget_enforcement
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns the effective per-agent daily limit
|
|
127
|
+
#
|
|
128
|
+
# @param agent_type [String] The agent class name
|
|
129
|
+
# @return [Float, nil] The limit or nil if not set
|
|
130
|
+
def effective_per_agent_daily(agent_type)
|
|
131
|
+
limit = per_agent_daily&.dig(agent_type)
|
|
132
|
+
return limit if limit.present?
|
|
133
|
+
return nil unless inherit_global_defaults
|
|
134
|
+
|
|
135
|
+
global_config&.dig(:per_agent_daily, agent_type)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns the effective per-agent monthly limit
|
|
139
|
+
#
|
|
140
|
+
# @param agent_type [String] The agent class name
|
|
141
|
+
# @return [Float, nil] The limit or nil if not set
|
|
142
|
+
def effective_per_agent_monthly(agent_type)
|
|
143
|
+
limit = per_agent_monthly&.dig(agent_type)
|
|
144
|
+
return limit if limit.present?
|
|
145
|
+
return nil unless inherit_global_defaults
|
|
146
|
+
|
|
147
|
+
global_config&.dig(:per_agent_monthly, agent_type)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Budget status checks
|
|
151
|
+
|
|
152
|
+
# Checks if budget enforcement is enabled
|
|
153
|
+
#
|
|
154
|
+
# @return [Boolean] true if enforcement is :soft or :hard
|
|
155
|
+
def budgets_enabled?
|
|
156
|
+
effective_enforcement != :none
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Checks if hard enforcement is enabled
|
|
160
|
+
#
|
|
161
|
+
# @return [Boolean]
|
|
162
|
+
def hard_enforcement?
|
|
163
|
+
effective_enforcement == :hard
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Checks if soft enforcement is enabled
|
|
167
|
+
#
|
|
168
|
+
# @return [Boolean]
|
|
169
|
+
def soft_enforcement?
|
|
170
|
+
effective_enforcement == :soft
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Check if within budget for a specific type
|
|
174
|
+
#
|
|
175
|
+
# @param type [Symbol] :daily_cost, :monthly_cost, :daily_tokens,
|
|
176
|
+
# :monthly_tokens, :daily_executions, :monthly_executions
|
|
177
|
+
# @return [Boolean]
|
|
178
|
+
def within_budget?(type: :daily_cost)
|
|
179
|
+
status = budget_status
|
|
180
|
+
return true unless status[:enabled]
|
|
181
|
+
|
|
182
|
+
key = budget_status_key(type)
|
|
183
|
+
(status.dig(key, :percentage_used) || 0) < 100
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Get remaining budget for a specific type
|
|
187
|
+
#
|
|
188
|
+
# @param type [Symbol] Budget type (see #within_budget?)
|
|
189
|
+
# @return [Numeric, nil]
|
|
190
|
+
def remaining_budget(type: :daily_cost)
|
|
191
|
+
status = budget_status
|
|
192
|
+
key = budget_status_key(type)
|
|
193
|
+
status.dig(key, :remaining)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Check budget and raise if exceeded (for hard enforcement)
|
|
197
|
+
#
|
|
198
|
+
# @param agent_type [String] The agent class name
|
|
199
|
+
# @raise [BudgetExceededError] If hard enforcement and over budget
|
|
200
|
+
def check_budget!(agent_type = nil)
|
|
201
|
+
BudgetTracker.check_budget!(agent_type || "Unknown", tenant_id: tenant_id)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Get full budget status from BudgetTracker
|
|
205
|
+
#
|
|
206
|
+
# @return [Hash] Budget status with usage information
|
|
207
|
+
def budget_status
|
|
208
|
+
BudgetTracker.status(tenant_id: tenant_id)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Convert to config hash for BudgetTracker
|
|
212
|
+
#
|
|
213
|
+
# @return [Hash] Budget configuration hash
|
|
214
|
+
def to_budget_config
|
|
215
|
+
{
|
|
216
|
+
enabled: budgets_enabled?,
|
|
217
|
+
enforcement: effective_enforcement,
|
|
218
|
+
# Cost limits
|
|
219
|
+
global_daily: effective_daily_limit,
|
|
220
|
+
global_monthly: effective_monthly_limit,
|
|
221
|
+
per_agent_daily: merged_per_agent_daily,
|
|
222
|
+
per_agent_monthly: merged_per_agent_monthly,
|
|
223
|
+
# Token limits
|
|
224
|
+
global_daily_tokens: effective_daily_token_limit,
|
|
225
|
+
global_monthly_tokens: effective_monthly_token_limit,
|
|
226
|
+
# Execution limits
|
|
227
|
+
global_daily_executions: effective_daily_execution_limit,
|
|
228
|
+
global_monthly_executions: effective_monthly_execution_limit
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
# Returns the global budgets configuration
|
|
235
|
+
#
|
|
236
|
+
# @return [Hash, nil]
|
|
237
|
+
def global_config
|
|
238
|
+
RubyLLM::Agents.configuration.budgets
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Merges per-agent daily limits with global defaults
|
|
242
|
+
#
|
|
243
|
+
# @return [Hash]
|
|
244
|
+
def merged_per_agent_daily
|
|
245
|
+
return per_agent_daily || {} unless inherit_global_defaults
|
|
246
|
+
|
|
247
|
+
(global_config&.dig(:per_agent_daily) || {}).merge(per_agent_daily || {})
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Merges per-agent monthly limits with global defaults
|
|
251
|
+
#
|
|
252
|
+
# @return [Hash]
|
|
253
|
+
def merged_per_agent_monthly
|
|
254
|
+
return per_agent_monthly || {} unless inherit_global_defaults
|
|
255
|
+
|
|
256
|
+
(global_config&.dig(:per_agent_monthly) || {}).merge(per_agent_monthly || {})
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Maps budget type to status key
|
|
260
|
+
#
|
|
261
|
+
# @param type [Symbol]
|
|
262
|
+
# @return [Symbol]
|
|
263
|
+
def budget_status_key(type)
|
|
264
|
+
case type
|
|
265
|
+
when :daily_cost then :global_daily
|
|
266
|
+
when :monthly_cost then :global_monthly
|
|
267
|
+
when :daily_tokens then :global_daily_tokens
|
|
268
|
+
when :monthly_tokens then :global_monthly_tokens
|
|
269
|
+
when :daily_executions then :global_daily_executions
|
|
270
|
+
when :monthly_executions then :global_monthly_executions
|
|
271
|
+
else :global_daily
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
class Tenant
|
|
8
|
+
# Manages API configuration for a tenant.
|
|
9
|
+
#
|
|
10
|
+
# Links to the ApiConfiguration model to provide per-tenant API keys
|
|
11
|
+
# and settings. Supports inheritance from global configuration when
|
|
12
|
+
# tenant-specific settings are not defined.
|
|
13
|
+
#
|
|
14
|
+
# @example Accessing tenant API keys
|
|
15
|
+
# tenant = Tenant.for("acme_corp")
|
|
16
|
+
# tenant.api_key_for(:openai) # => "sk-..."
|
|
17
|
+
# tenant.has_custom_api_keys? # => true
|
|
18
|
+
#
|
|
19
|
+
# @example Getting effective configuration
|
|
20
|
+
# config = tenant.effective_api_configuration
|
|
21
|
+
# config.apply_to_ruby_llm!
|
|
22
|
+
#
|
|
23
|
+
# @see ApiConfiguration
|
|
24
|
+
# @api public
|
|
25
|
+
module Configurable
|
|
26
|
+
extend ActiveSupport::Concern
|
|
27
|
+
|
|
28
|
+
included do
|
|
29
|
+
# Link to tenant-specific API configuration
|
|
30
|
+
has_one :api_configuration,
|
|
31
|
+
-> { where(scope_type: "tenant") },
|
|
32
|
+
class_name: "RubyLLM::Agents::ApiConfiguration",
|
|
33
|
+
foreign_key: :scope_id,
|
|
34
|
+
primary_key: :tenant_id,
|
|
35
|
+
dependent: :destroy
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get API key for a specific provider
|
|
39
|
+
#
|
|
40
|
+
# @param provider [Symbol] Provider name (:openai, :anthropic, :gemini, etc.)
|
|
41
|
+
# @return [String, nil] The API key or nil if not configured
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# tenant.api_key_for(:openai) # => "sk-abc123..."
|
|
45
|
+
# tenant.api_key_for(:anthropic) # => "sk-ant-xyz..."
|
|
46
|
+
def api_key_for(provider)
|
|
47
|
+
attr_name = "#{provider}_api_key"
|
|
48
|
+
api_configuration&.send(attr_name) if api_configuration&.respond_to?(attr_name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Check if tenant has custom API keys configured
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean] true if tenant has an ApiConfiguration record
|
|
54
|
+
def has_custom_api_keys?
|
|
55
|
+
api_configuration.present?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get effective API configuration for this tenant
|
|
59
|
+
#
|
|
60
|
+
# Returns the resolved configuration that combines tenant-specific
|
|
61
|
+
# settings with global defaults.
|
|
62
|
+
#
|
|
63
|
+
# @return [ResolvedConfig] The resolved configuration
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
# config = tenant.effective_api_configuration
|
|
67
|
+
# config.openai_api_key # Tenant's key or global fallback
|
|
68
|
+
def effective_api_configuration
|
|
69
|
+
ApiConfiguration.resolve(tenant_id: tenant_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get or create the API configuration for this tenant
|
|
73
|
+
#
|
|
74
|
+
# @return [ApiConfiguration] The tenant's API configuration record
|
|
75
|
+
def api_configuration!
|
|
76
|
+
api_configuration || create_api_configuration!(
|
|
77
|
+
scope_type: "tenant",
|
|
78
|
+
scope_id: tenant_id
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Configure API settings for this tenant
|
|
83
|
+
#
|
|
84
|
+
# @yield [config] Block to configure the API settings
|
|
85
|
+
# @yieldparam config [ApiConfiguration] The configuration to modify
|
|
86
|
+
# @return [ApiConfiguration] The saved configuration
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# tenant.configure_api do |config|
|
|
90
|
+
# config.openai_api_key = "sk-..."
|
|
91
|
+
# config.default_model = "gpt-4o"
|
|
92
|
+
# end
|
|
93
|
+
def configure_api(&block)
|
|
94
|
+
config = api_configuration!
|
|
95
|
+
yield(config) if block_given?
|
|
96
|
+
config.save!
|
|
97
|
+
config
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Check if a specific provider is configured for this tenant
|
|
101
|
+
#
|
|
102
|
+
# @param provider [Symbol] Provider name
|
|
103
|
+
# @return [Boolean] true if the provider has an API key set
|
|
104
|
+
def provider_configured?(provider)
|
|
105
|
+
api_key_for(provider).present?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get all configured providers for this tenant
|
|
109
|
+
#
|
|
110
|
+
# @return [Array<Symbol>] List of configured provider symbols
|
|
111
|
+
def configured_providers
|
|
112
|
+
return [] unless api_configuration
|
|
113
|
+
|
|
114
|
+
ApiConfiguration::PROVIDERS.keys.select do |provider|
|
|
115
|
+
provider_configured?(provider)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get the default model for this tenant
|
|
120
|
+
#
|
|
121
|
+
# @return [String, nil] The default model or nil
|
|
122
|
+
def default_model
|
|
123
|
+
api_configuration&.default_model
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Get the default embedding model for this tenant
|
|
127
|
+
#
|
|
128
|
+
# @return [String, nil] The default embedding model or nil
|
|
129
|
+
def default_embedding_model
|
|
130
|
+
api_configuration&.default_embedding_model
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|