ruby_llm-agents 1.1.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/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/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
- data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
- 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/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
- 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/budget/config_resolver.rb +4 -2
- metadata +7 -1
|
@@ -2,262 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Agents
|
|
5
|
-
#
|
|
5
|
+
# @deprecated Use {Tenant} instead. This class will be removed in a future major version.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# Supports cost-based (USD), token-based, and execution-based limits.
|
|
7
|
+
# TenantBudget is now an alias to Tenant for backward compatibility.
|
|
8
|
+
# All functionality has been moved to the Tenant model with organized concerns.
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# @!attribute [rw] daily_limit
|
|
16
|
-
# @return [BigDecimal, nil] Daily budget limit in USD
|
|
17
|
-
# @!attribute [rw] monthly_limit
|
|
18
|
-
# @return [BigDecimal, nil] Monthly budget limit in USD
|
|
19
|
-
# @!attribute [rw] daily_token_limit
|
|
20
|
-
# @return [Integer, nil] Daily token limit (across all models)
|
|
21
|
-
# @!attribute [rw] monthly_token_limit
|
|
22
|
-
# @return [Integer, nil] Monthly token limit (across all models)
|
|
23
|
-
# @!attribute [rw] daily_execution_limit
|
|
24
|
-
# @return [Integer, nil] Daily execution/call limit
|
|
25
|
-
# @!attribute [rw] monthly_execution_limit
|
|
26
|
-
# @return [Integer, nil] Monthly execution/call limit
|
|
27
|
-
# @!attribute [rw] per_agent_daily
|
|
28
|
-
# @return [Hash] Per-agent daily cost limits: { "AgentName" => limit }
|
|
29
|
-
# @!attribute [rw] per_agent_monthly
|
|
30
|
-
# @return [Hash] Per-agent monthly cost limits: { "AgentName" => limit }
|
|
31
|
-
# @!attribute [rw] enforcement
|
|
32
|
-
# @return [String] Enforcement mode: "none", "soft", or "hard"
|
|
33
|
-
# @!attribute [rw] inherit_global_defaults
|
|
34
|
-
# @return [Boolean] Whether to fall back to global config for unset limits
|
|
35
|
-
# @!attribute [rw] tenant_record
|
|
36
|
-
# @return [ActiveRecord::Base, nil] Polymorphic association to tenant model
|
|
10
|
+
# @example Migration path
|
|
11
|
+
# # Old usage (still works)
|
|
12
|
+
# TenantBudget.for_tenant("acme_corp")
|
|
13
|
+
# TenantBudget.create!(tenant_id: "acme", daily_limit: 100)
|
|
37
14
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# name: "Acme Corporation",
|
|
42
|
-
# daily_limit: 50.0, # USD
|
|
43
|
-
# monthly_limit: 500.0, # USD
|
|
44
|
-
# daily_token_limit: 1_000_000,
|
|
45
|
-
# monthly_token_limit: 10_000_000,
|
|
46
|
-
# daily_execution_limit: 500,
|
|
47
|
-
# monthly_execution_limit: 10_000,
|
|
48
|
-
# enforcement: "hard"
|
|
49
|
-
# )
|
|
15
|
+
# # New usage (preferred)
|
|
16
|
+
# Tenant.for("acme_corp")
|
|
17
|
+
# Tenant.create!(tenant_id: "acme", daily_limit: 100)
|
|
50
18
|
#
|
|
51
|
-
# @
|
|
52
|
-
|
|
53
|
-
# budget.effective_daily_limit # => 50.0 (cost)
|
|
54
|
-
# budget.effective_daily_token_limit # => 1_000_000 (tokens)
|
|
55
|
-
# budget.effective_daily_execution_limit # => 500 (executions)
|
|
56
|
-
#
|
|
57
|
-
# @see RubyLLM::Agents::BudgetTracker
|
|
58
|
-
# @see RubyLLM::Agents::LLMTenant
|
|
59
|
-
# @api public
|
|
60
|
-
class TenantBudget < ::ActiveRecord::Base
|
|
61
|
-
self.table_name = "ruby_llm_agents_tenant_budgets"
|
|
62
|
-
|
|
63
|
-
# Valid enforcement modes
|
|
64
|
-
ENFORCEMENT_MODES = %w[none soft hard].freeze
|
|
65
|
-
|
|
66
|
-
# Polymorphic association to the tenant model (e.g., Organization, Account)
|
|
67
|
-
belongs_to :tenant_record, polymorphic: true, optional: true
|
|
68
|
-
|
|
69
|
-
# Validations
|
|
70
|
-
validates :tenant_id, presence: true, uniqueness: true
|
|
71
|
-
validates :enforcement, inclusion: { in: ENFORCEMENT_MODES }, allow_nil: true
|
|
72
|
-
validates :daily_limit, :monthly_limit,
|
|
73
|
-
numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
74
|
-
validates :daily_token_limit, :monthly_token_limit,
|
|
75
|
-
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
76
|
-
validates :daily_execution_limit, :monthly_execution_limit,
|
|
77
|
-
numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_nil: true
|
|
78
|
-
|
|
79
|
-
# Finds a budget for the given tenant
|
|
80
|
-
#
|
|
81
|
-
# @param tenant [String, Object] The tenant identifier string or object with llm_tenant_id
|
|
82
|
-
# @return [TenantBudget, nil] The budget record or nil if not found
|
|
83
|
-
def self.for_tenant(tenant)
|
|
84
|
-
return nil if tenant.blank?
|
|
85
|
-
|
|
86
|
-
if tenant.respond_to?(:llm_tenant_id)
|
|
87
|
-
# Object with llm_tenant DSL - try polymorphic first, then tenant_id
|
|
88
|
-
find_by(tenant_record: tenant) || find_by(tenant_id: tenant.llm_tenant_id)
|
|
89
|
-
else
|
|
90
|
-
# String tenant_id
|
|
91
|
-
find_by(tenant_id: tenant.to_s)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Finds or creates a budget for the given tenant
|
|
96
|
-
#
|
|
97
|
-
# @param tenant_id [String] The tenant identifier
|
|
98
|
-
# @param name [String, nil] Optional human-readable name
|
|
99
|
-
# @return [TenantBudget] The budget record
|
|
100
|
-
def self.for_tenant!(tenant_id, name: nil)
|
|
101
|
-
find_or_create_by!(tenant_id: tenant_id) do |budget|
|
|
102
|
-
budget.name = name
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Returns the display name (name or tenant_id fallback)
|
|
107
|
-
#
|
|
108
|
-
# @return [String] The name to display
|
|
109
|
-
def display_name
|
|
110
|
-
name.presence || tenant_id
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Returns the effective daily limit, considering inheritance
|
|
114
|
-
#
|
|
115
|
-
# @return [Float, nil] The daily limit or nil if not set
|
|
116
|
-
def effective_daily_limit
|
|
117
|
-
return daily_limit if daily_limit.present?
|
|
118
|
-
return nil unless inherit_global_defaults
|
|
119
|
-
|
|
120
|
-
global_config&.dig(:global_daily)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Returns the effective monthly limit, considering inheritance
|
|
124
|
-
#
|
|
125
|
-
# @return [Float, nil] The monthly limit or nil if not set
|
|
126
|
-
def effective_monthly_limit
|
|
127
|
-
return monthly_limit if monthly_limit.present?
|
|
128
|
-
return nil unless inherit_global_defaults
|
|
129
|
-
|
|
130
|
-
global_config&.dig(:global_monthly)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Returns the effective per-agent daily limit
|
|
134
|
-
#
|
|
135
|
-
# @param agent_type [String] The agent class name
|
|
136
|
-
# @return [Float, nil] The limit or nil if not set
|
|
137
|
-
def effective_per_agent_daily(agent_type)
|
|
138
|
-
limit = per_agent_daily&.dig(agent_type)
|
|
139
|
-
return limit if limit.present?
|
|
140
|
-
return nil unless inherit_global_defaults
|
|
141
|
-
|
|
142
|
-
global_config&.dig(:per_agent_daily, agent_type)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Returns the effective per-agent monthly limit
|
|
146
|
-
#
|
|
147
|
-
# @param agent_type [String] The agent class name
|
|
148
|
-
# @return [Float, nil] The limit or nil if not set
|
|
149
|
-
def effective_per_agent_monthly(agent_type)
|
|
150
|
-
limit = per_agent_monthly&.dig(agent_type)
|
|
151
|
-
return limit if limit.present?
|
|
152
|
-
return nil unless inherit_global_defaults
|
|
153
|
-
|
|
154
|
-
global_config&.dig(:per_agent_monthly, agent_type)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Returns the effective daily token limit, considering inheritance
|
|
158
|
-
#
|
|
159
|
-
# @return [Integer, nil] The daily token limit or nil if not set
|
|
160
|
-
def effective_daily_token_limit
|
|
161
|
-
return daily_token_limit if daily_token_limit.present?
|
|
162
|
-
return nil unless inherit_global_defaults
|
|
163
|
-
|
|
164
|
-
global_config&.dig(:global_daily_tokens)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Returns the effective monthly token limit, considering inheritance
|
|
168
|
-
#
|
|
169
|
-
# @return [Integer, nil] The monthly token limit or nil if not set
|
|
170
|
-
def effective_monthly_token_limit
|
|
171
|
-
return monthly_token_limit if monthly_token_limit.present?
|
|
172
|
-
return nil unless inherit_global_defaults
|
|
173
|
-
|
|
174
|
-
global_config&.dig(:global_monthly_tokens)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Returns the effective daily execution limit, considering inheritance
|
|
178
|
-
#
|
|
179
|
-
# @return [Integer, nil] The daily execution limit or nil if not set
|
|
180
|
-
def effective_daily_execution_limit
|
|
181
|
-
return daily_execution_limit if daily_execution_limit.present?
|
|
182
|
-
return nil unless inherit_global_defaults
|
|
183
|
-
|
|
184
|
-
global_config&.dig(:global_daily_executions)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Returns the effective monthly execution limit, considering inheritance
|
|
188
|
-
#
|
|
189
|
-
# @return [Integer, nil] The monthly execution limit or nil if not set
|
|
190
|
-
def effective_monthly_execution_limit
|
|
191
|
-
return monthly_execution_limit if monthly_execution_limit.present?
|
|
192
|
-
return nil unless inherit_global_defaults
|
|
193
|
-
|
|
194
|
-
global_config&.dig(:global_monthly_executions)
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Returns the effective enforcement mode
|
|
198
|
-
#
|
|
199
|
-
# @return [Symbol] :none, :soft, or :hard
|
|
200
|
-
def effective_enforcement
|
|
201
|
-
return enforcement.to_sym if enforcement.present?
|
|
202
|
-
return :soft unless inherit_global_defaults
|
|
203
|
-
|
|
204
|
-
RubyLLM::Agents.configuration.budget_enforcement
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
# Checks if budget enforcement is enabled for this tenant
|
|
208
|
-
#
|
|
209
|
-
# @return [Boolean] true if enforcement is :soft or :hard
|
|
210
|
-
def budgets_enabled?
|
|
211
|
-
effective_enforcement != :none
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Returns a hash suitable for BudgetTracker
|
|
215
|
-
#
|
|
216
|
-
# @return [Hash] Budget configuration hash
|
|
217
|
-
def to_budget_config
|
|
218
|
-
{
|
|
219
|
-
enabled: budgets_enabled?,
|
|
220
|
-
enforcement: effective_enforcement,
|
|
221
|
-
# Cost limits
|
|
222
|
-
global_daily: effective_daily_limit,
|
|
223
|
-
global_monthly: effective_monthly_limit,
|
|
224
|
-
per_agent_daily: merged_per_agent_daily,
|
|
225
|
-
per_agent_monthly: merged_per_agent_monthly,
|
|
226
|
-
# Token limits
|
|
227
|
-
global_daily_tokens: effective_daily_token_limit,
|
|
228
|
-
global_monthly_tokens: effective_monthly_token_limit,
|
|
229
|
-
# Execution limits
|
|
230
|
-
global_daily_executions: effective_daily_execution_limit,
|
|
231
|
-
global_monthly_executions: effective_monthly_execution_limit
|
|
232
|
-
}
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
private
|
|
236
|
-
|
|
237
|
-
# Returns the global budgets configuration
|
|
238
|
-
#
|
|
239
|
-
# @return [Hash, nil] Global budget config
|
|
240
|
-
def global_config
|
|
241
|
-
RubyLLM::Agents.configuration.budgets
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Merges per-agent daily limits with global defaults
|
|
245
|
-
#
|
|
246
|
-
# @return [Hash] Merged per-agent daily limits
|
|
247
|
-
def merged_per_agent_daily
|
|
248
|
-
return per_agent_daily || {} unless inherit_global_defaults
|
|
249
|
-
|
|
250
|
-
(global_config&.dig(:per_agent_daily) || {}).merge(per_agent_daily || {})
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Merges per-agent monthly limits with global defaults
|
|
254
|
-
#
|
|
255
|
-
# @return [Hash] Merged per-agent monthly limits
|
|
256
|
-
def merged_per_agent_monthly
|
|
257
|
-
return per_agent_monthly || {} unless inherit_global_defaults
|
|
258
|
-
|
|
259
|
-
(global_config&.dig(:per_agent_monthly) || {}).merge(per_agent_monthly || {})
|
|
260
|
-
end
|
|
261
|
-
end
|
|
19
|
+
# @see Tenant
|
|
20
|
+
TenantBudget = Tenant
|
|
262
21
|
end
|
|
263
22
|
end
|
|
@@ -10,8 +10,11 @@ module RubyLlmAgents
|
|
|
10
10
|
# rails generate ruby_llm_agents:multi_tenancy
|
|
11
11
|
#
|
|
12
12
|
# This will create migrations for:
|
|
13
|
-
# -
|
|
14
|
-
# - Adding
|
|
13
|
+
# - ruby_llm_agents_tenants table for per-tenant configuration
|
|
14
|
+
# - Adding tenant columns to ruby_llm_agents_executions
|
|
15
|
+
#
|
|
16
|
+
# For users upgrading from an older version:
|
|
17
|
+
# - Renames ruby_llm_agents_tenant_budgets to ruby_llm_agents_tenants
|
|
15
18
|
#
|
|
16
19
|
class MultiTenancyGenerator < ::Rails::Generators::Base
|
|
17
20
|
include ::ActiveRecord::Generators::Migration
|
|
@@ -20,21 +23,31 @@ module RubyLlmAgents
|
|
|
20
23
|
|
|
21
24
|
desc "Adds multi-tenancy support to RubyLLM::Agents"
|
|
22
25
|
|
|
23
|
-
def
|
|
24
|
-
if table_exists?(:
|
|
25
|
-
say_status :skip, "
|
|
26
|
+
def create_tenants_migration
|
|
27
|
+
if table_exists?(:ruby_llm_agents_tenants)
|
|
28
|
+
say_status :skip, "ruby_llm_agents_tenants table already exists", :yellow
|
|
26
29
|
return
|
|
27
30
|
end
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
if table_exists?(:ruby_llm_agents_tenant_budgets)
|
|
33
|
+
# Upgrade path: rename existing table
|
|
34
|
+
say_status :upgrade, "Renaming tenant_budgets to tenants", :blue
|
|
35
|
+
migration_template(
|
|
36
|
+
"rename_tenant_budgets_to_tenants_migration.rb.tt",
|
|
37
|
+
File.join(db_migrate_path, "rename_tenant_budgets_to_tenants.rb")
|
|
38
|
+
)
|
|
39
|
+
else
|
|
40
|
+
# Fresh install: create new table
|
|
41
|
+
migration_template(
|
|
42
|
+
"create_tenants_migration.rb.tt",
|
|
43
|
+
File.join(db_migrate_path, "create_ruby_llm_agents_tenants.rb")
|
|
44
|
+
)
|
|
45
|
+
end
|
|
33
46
|
end
|
|
34
47
|
|
|
35
48
|
def create_add_tenant_to_executions_migration
|
|
36
49
|
if column_exists?(:ruby_llm_agents_executions, :tenant_id)
|
|
37
|
-
say_status :skip, "tenant_id column already exists", :yellow
|
|
50
|
+
say_status :skip, "tenant_id column already exists on executions", :yellow
|
|
38
51
|
return
|
|
39
52
|
end
|
|
40
53
|
|
|
@@ -50,23 +63,30 @@ module RubyLlmAgents
|
|
|
50
63
|
say ""
|
|
51
64
|
say "Next steps:"
|
|
52
65
|
say " 1. Run: rails db:migrate"
|
|
53
|
-
say " 2.
|
|
66
|
+
say " 2. Add llm_tenant to your tenant model:"
|
|
54
67
|
say ""
|
|
55
|
-
say "
|
|
56
|
-
say "
|
|
57
|
-
say "
|
|
68
|
+
say " class Organization < ApplicationRecord"
|
|
69
|
+
say " include RubyLLM::Agents::LLMTenant"
|
|
70
|
+
say ""
|
|
71
|
+
say " llm_tenant id: :id, # Method for tenant_id"
|
|
72
|
+
say " budget: true, # Auto-create budget on creation"
|
|
73
|
+
say " limits: { # Optional default limits"
|
|
74
|
+
say " daily_cost: 100,"
|
|
75
|
+
say " monthly_cost: 1000"
|
|
76
|
+
say " },"
|
|
77
|
+
say " enforcement: :hard # :none, :soft, or :hard"
|
|
58
78
|
say " end"
|
|
59
79
|
say ""
|
|
60
|
-
say " 3.
|
|
80
|
+
say " 3. Pass tenant to agents:"
|
|
81
|
+
say ""
|
|
82
|
+
say " MyAgent.call(prompt, tenant: current_organization)"
|
|
61
83
|
say ""
|
|
62
|
-
say " 4.
|
|
84
|
+
say " 4. Query usage:"
|
|
63
85
|
say ""
|
|
64
|
-
say " RubyLLM::Agents::
|
|
65
|
-
say "
|
|
66
|
-
say "
|
|
67
|
-
say "
|
|
68
|
-
say " enforcement: 'hard'"
|
|
69
|
-
say " )"
|
|
86
|
+
say " tenant = RubyLLM::Agents::Tenant.for(organization)"
|
|
87
|
+
say " tenant.cost_today # => 12.34"
|
|
88
|
+
say " tenant.tokens_this_month # => 50000"
|
|
89
|
+
say " tenant.usage_summary # => { cost: ..., tokens: ..., ... }"
|
|
70
90
|
say ""
|
|
71
91
|
end
|
|
72
92
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Migration to add
|
|
3
|
+
# Migration to add tenant columns to executions for multi-tenancy support
|
|
4
4
|
#
|
|
5
|
-
# This migration adds
|
|
5
|
+
# This migration adds tenant columns to track which tenant each execution
|
|
6
6
|
# belongs to, enabling:
|
|
7
7
|
# - Filtering executions by tenant
|
|
8
8
|
# - Tenant-scoped analytics and reporting
|
|
9
9
|
# - Per-tenant budget tracking and circuit breakers
|
|
10
|
+
# - Polymorphic association with tenant models (e.g., Organization, Account)
|
|
10
11
|
#
|
|
11
12
|
# Run with: rails db:migrate
|
|
12
13
|
class AddTenantIdToRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migration_version %>
|
|
@@ -14,10 +15,20 @@ class AddTenantIdToRubyLLMAgentsExecutions < ActiveRecord::Migration<%= migratio
|
|
|
14
15
|
# Add tenant_id column (nullable for backward compatibility)
|
|
15
16
|
add_column :ruby_llm_agents_executions, :tenant_id, :string
|
|
16
17
|
|
|
18
|
+
# Polymorphic association to tenant model (for llm_tenant DSL)
|
|
19
|
+
# Uses string type for tenant_record_id to support both integer and UUID primary keys
|
|
20
|
+
add_column :ruby_llm_agents_executions, :tenant_record_type, :string
|
|
21
|
+
add_column :ruby_llm_agents_executions, :tenant_record_id, :string
|
|
22
|
+
|
|
17
23
|
# Add indexes for efficient tenant-scoped queries
|
|
18
24
|
add_index :ruby_llm_agents_executions, :tenant_id
|
|
19
25
|
add_index :ruby_llm_agents_executions, [:tenant_id, :created_at]
|
|
20
26
|
add_index :ruby_llm_agents_executions, [:tenant_id, :agent_type]
|
|
21
27
|
add_index :ruby_llm_agents_executions, [:tenant_id, :status]
|
|
28
|
+
|
|
29
|
+
# Index for polymorphic tenant record lookup
|
|
30
|
+
add_index :ruby_llm_agents_executions,
|
|
31
|
+
[:tenant_record_type, :tenant_record_id],
|
|
32
|
+
name: "index_executions_on_tenant_record"
|
|
22
33
|
end
|
|
23
34
|
end
|
|
@@ -36,10 +36,21 @@ class CreateRubyLLMAgentsTenantBudgets < ActiveRecord::Migration<%= migration_ve
|
|
|
36
36
|
# Whether to inherit from global config for unset limits
|
|
37
37
|
t.boolean :inherit_global_defaults, default: true
|
|
38
38
|
|
|
39
|
+
# Polymorphic association to tenant model (e.g., Organization, Account)
|
|
40
|
+
# Allows direct association with ActiveRecord models using llm_tenant DSL
|
|
41
|
+
# Uses string type for tenant_record_id to support both integer and UUID primary keys
|
|
42
|
+
t.string :tenant_record_type
|
|
43
|
+
t.string :tenant_record_id
|
|
44
|
+
|
|
39
45
|
t.timestamps
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# Ensure unique tenant IDs
|
|
43
49
|
add_index :ruby_llm_agents_tenant_budgets, :tenant_id, unique: true
|
|
50
|
+
|
|
51
|
+
# Index for polymorphic tenant record lookup
|
|
52
|
+
add_index :ruby_llm_agents_tenant_budgets,
|
|
53
|
+
[:tenant_record_type, :tenant_record_id],
|
|
54
|
+
name: "index_tenant_budgets_on_tenant_record"
|
|
44
55
|
end
|
|
45
56
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to create the tenants table for multi-tenancy support
|
|
4
|
+
#
|
|
5
|
+
# This table stores tenant configuration including:
|
|
6
|
+
# - Budget limits (cost, tokens, executions)
|
|
7
|
+
# - Enforcement mode (none, soft, hard)
|
|
8
|
+
# - Polymorphic link to user's tenant model
|
|
9
|
+
#
|
|
10
|
+
# Run with: rails db:migrate
|
|
11
|
+
class CreateRubyLLMAgentsTenants < ActiveRecord::Migration<%= migration_version %>
|
|
12
|
+
def change
|
|
13
|
+
create_table :ruby_llm_agents_tenants do |t|
|
|
14
|
+
# Identity
|
|
15
|
+
t.string :tenant_id, null: false
|
|
16
|
+
t.string :name
|
|
17
|
+
|
|
18
|
+
# Polymorphic association to user's tenant model (e.g., Organization, Account)
|
|
19
|
+
# Uses string type for tenant_record_id to support both integer and UUID primary keys
|
|
20
|
+
t.string :tenant_record_type
|
|
21
|
+
t.string :tenant_record_id
|
|
22
|
+
|
|
23
|
+
# Cost limits (in USD, 6 decimal precision)
|
|
24
|
+
t.decimal :daily_limit, precision: 12, scale: 6
|
|
25
|
+
t.decimal :monthly_limit, precision: 12, scale: 6
|
|
26
|
+
|
|
27
|
+
# Token limits
|
|
28
|
+
t.bigint :daily_token_limit
|
|
29
|
+
t.bigint :monthly_token_limit
|
|
30
|
+
|
|
31
|
+
# Execution/call limits
|
|
32
|
+
t.bigint :daily_execution_limit
|
|
33
|
+
t.bigint :monthly_execution_limit
|
|
34
|
+
|
|
35
|
+
# Per-agent limits (JSON hash)
|
|
36
|
+
# Format: { "AgentName" => limit_value }
|
|
37
|
+
t.json :per_agent_daily, null: false, default: {}
|
|
38
|
+
t.json :per_agent_monthly, null: false, default: {}
|
|
39
|
+
|
|
40
|
+
# Enforcement mode: "none", "soft", or "hard"
|
|
41
|
+
# - none: no enforcement, only tracking
|
|
42
|
+
# - soft: log warnings when limits exceeded
|
|
43
|
+
# - hard: block execution when limits exceeded
|
|
44
|
+
t.string :enforcement, default: "soft"
|
|
45
|
+
|
|
46
|
+
# Whether to inherit from global config for unset limits
|
|
47
|
+
t.boolean :inherit_global_defaults, default: true
|
|
48
|
+
|
|
49
|
+
# Status (for soft-delete/disable)
|
|
50
|
+
t.boolean :active, default: true
|
|
51
|
+
|
|
52
|
+
# Extensible metadata (JSON)
|
|
53
|
+
t.json :metadata, null: false, default: {}
|
|
54
|
+
|
|
55
|
+
t.timestamps
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Ensure unique tenant IDs
|
|
59
|
+
add_index :ruby_llm_agents_tenants, :tenant_id, unique: true
|
|
60
|
+
|
|
61
|
+
# Index for name lookups
|
|
62
|
+
add_index :ruby_llm_agents_tenants, :name
|
|
63
|
+
|
|
64
|
+
# Index for active/inactive filtering
|
|
65
|
+
add_index :ruby_llm_agents_tenants, :active
|
|
66
|
+
|
|
67
|
+
# Index for polymorphic tenant record lookup
|
|
68
|
+
add_index :ruby_llm_agents_tenants,
|
|
69
|
+
[:tenant_record_type, :tenant_record_id],
|
|
70
|
+
name: "index_tenants_on_tenant_record"
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration to rename tenant_budgets table to tenants
|
|
4
|
+
#
|
|
5
|
+
# This migration is for users upgrading from an older version of the gem
|
|
6
|
+
# where the table was called 'ruby_llm_agents_tenant_budgets'.
|
|
7
|
+
#
|
|
8
|
+
# If you're doing a fresh install, use the create_tenants_migration instead.
|
|
9
|
+
#
|
|
10
|
+
# Run with: rails db:migrate
|
|
11
|
+
class RenameTenantBudgetsToTenants < ActiveRecord::Migration<%= migration_version %>
|
|
12
|
+
def change
|
|
13
|
+
# Rename the table
|
|
14
|
+
rename_table :ruby_llm_agents_tenant_budgets, :ruby_llm_agents_tenants
|
|
15
|
+
|
|
16
|
+
# Add new columns for Tenant model
|
|
17
|
+
add_column :ruby_llm_agents_tenants, :active, :boolean, default: true
|
|
18
|
+
add_column :ruby_llm_agents_tenants, :metadata, :json, null: false, default: {}
|
|
19
|
+
|
|
20
|
+
# Add index for active status
|
|
21
|
+
add_index :ruby_llm_agents_tenants, :active
|
|
22
|
+
|
|
23
|
+
# Rename indexes to match new table name
|
|
24
|
+
# Note: Some databases handle this automatically when renaming tables
|
|
25
|
+
# If you get an error about missing indexes, you may need to adjust this
|
|
26
|
+
|
|
27
|
+
# The polymorphic index needs to be renamed if it exists
|
|
28
|
+
if index_exists?(:ruby_llm_agents_tenants, [:tenant_record_type, :tenant_record_id], name: "index_tenant_budgets_on_tenant_record")
|
|
29
|
+
rename_index :ruby_llm_agents_tenants,
|
|
30
|
+
"index_tenant_budgets_on_tenant_record",
|
|
31
|
+
"index_tenants_on_tenant_record"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -147,6 +147,26 @@ module RubyLlmAgents
|
|
|
147
147
|
)
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
+
def create_rename_tenant_budgets_migration
|
|
151
|
+
# Skip if already using new table name
|
|
152
|
+
if table_exists?(:ruby_llm_agents_tenants)
|
|
153
|
+
say_status :skip, "ruby_llm_agents_tenants table already exists", :yellow
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Only run if old table exists (needs upgrade)
|
|
158
|
+
unless table_exists?(:ruby_llm_agents_tenant_budgets)
|
|
159
|
+
say_status :skip, "No tenant_budgets table to upgrade", :yellow
|
|
160
|
+
return
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
say_status :upgrade, "Renaming tenant_budgets to tenants", :blue
|
|
164
|
+
migration_template(
|
|
165
|
+
"rename_tenant_budgets_to_tenants_migration.rb.tt",
|
|
166
|
+
File.join(db_migrate_path, "rename_tenant_budgets_to_tenants.rb")
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
150
170
|
def migrate_agents_directory
|
|
151
171
|
root_dir = RubyLLM::Agents.configuration.root_directory
|
|
152
172
|
namespace = RubyLLM::Agents.configuration.root_namespace
|
|
@@ -211,6 +231,12 @@ module RubyLlmAgents
|
|
|
211
231
|
false
|
|
212
232
|
end
|
|
213
233
|
|
|
234
|
+
def table_exists?(table)
|
|
235
|
+
ActiveRecord::Base.connection.table_exists?(table)
|
|
236
|
+
rescue StandardError
|
|
237
|
+
false
|
|
238
|
+
end
|
|
239
|
+
|
|
214
240
|
def migrate_directory(old_dir, new_dir, namespace)
|
|
215
241
|
source = Rails.root.join("app", old_dir)
|
|
216
242
|
destination = Rails.root.join("app", new_dir)
|