ruby_llm-agents 0.4.0 → 0.5.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +46 -13
  3. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  4. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  5. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  6. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  7. data/app/models/ruby_llm/agents/tenant_budget.rb +62 -7
  8. data/app/views/layouts/ruby_llm/agents/application.html.erb +3 -1
  9. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  10. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  11. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  12. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  13. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  14. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  15. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  16. data/app/views/ruby_llm/agents/executions/show.html.erb +82 -0
  17. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  18. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  19. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  20. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  21. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  22. data/config/routes.rb +12 -1
  23. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  24. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  25. data/lib/ruby_llm/agents/base/execution.rb +83 -0
  26. data/lib/ruby_llm/agents/base.rb +1 -0
  27. data/lib/ruby_llm/agents/budget_tracker.rb +285 -23
  28. data/lib/ruby_llm/agents/configuration.rb +38 -1
  29. data/lib/ruby_llm/agents/engine.rb +1 -0
  30. data/lib/ruby_llm/agents/instrumentation.rb +71 -3
  31. data/lib/ruby_llm/agents/resolved_config.rb +348 -0
  32. data/lib/ruby_llm/agents/version.rb +1 -1
  33. metadata +19 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d8bfa346ee4010060508948c994bc719f5f1ff40dd2eec3aeb04500f2054341
4
- data.tar.gz: 5546ca87104e12ba0522c2a6161ed2917355fb6fffcc7cc6dffd9920963b1037
3
+ metadata.gz: 4b0d9c86b501d77defab3fb33ae73b69548d332dad116dac76a56ac1dcaa3a60
4
+ data.tar.gz: 6ead43212abcb265fbb73b8daec26b49f8558c8962630d42279e3cf67328b263
5
5
  SHA512:
6
- metadata.gz: 391c1202b7bb677337329b5bd5358e9cb5bc4589d37cf48291bb4ff427d8ba1e3e7db99e8787d079ecd2f08382a4aa9940c573ad33c3be2040aa2e6ec21eb353
7
- data.tar.gz: 9b1ae37b9ca74f060377d32232a9c26a33e411d285ac658be9508348d75316582aa47e309b85c3258fcaf33a1d5e103fdef2aa1090569b86140b046ee7e975fc
6
+ metadata.gz: f5307aba39ba108d6104a39fcf842ab262d93237f07cc208291403f4fd1d23f604783b915c9236e85551caa0bf6e95bbe253159b4893cf174c3fb1dd12febe29
7
+ data.tar.gz: 0a72c3e6ae5912a9b694bd821ce514f6f4de8af7c99487d88bc9051ee80122f2f5e0e7bf97ec698d951a1e70012e676b15a21b9abbb22c777c9b677463d567e7
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # RubyLLM::Agents
2
2
 
3
+ > **AI Agents:** For comprehensive documentation optimized for AI consumption, see [LLMS.txt](LLMS.txt)
4
+
3
5
  > **Production-ready Rails engine for building, managing, and monitoring LLM-powered AI agents**
4
6
 
5
7
  [![Gem Version](https://badge.fury.io/rb/ruby_llm-agents.svg)](https://rubygems.org/gems/ruby_llm-agents)
@@ -22,11 +24,12 @@ Build intelligent AI agents in Ruby with a clean DSL, automatic execution tracki
22
24
 
23
25
  | Feature | Description | Docs |
24
26
  |---------|-------------|------|
25
- | **Agent DSL** | Declarative configuration with model, temperature, parameters | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) |
26
- | **Execution Tracking** | Automatic logging with token usage and cost analytics | [Tracking](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
27
- | **Cost Analytics** | Track spending by agent, model, and time period | [Analytics](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
28
- | **Reliability** | Automatic retries, model fallbacks, circuit breakers | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) |
27
+ | **Agent DSL** | Declarative configuration with model, temperature, parameters, description | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) |
28
+ | **Execution Tracking** | Automatic logging with token usage, cost analytics, and fallback tracking | [Tracking](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
29
+ | **Cost Analytics** | Track spending by agent, model, tenant, and time period | [Analytics](https://github.com/adham90/ruby_llm-agents/wiki/Execution-Tracking) |
30
+ | **Reliability** | Automatic retries, model fallbacks, circuit breakers with block DSL | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) |
29
31
  | **Budget Controls** | Daily/monthly limits with hard and soft enforcement | [Budgets](https://github.com/adham90/ruby_llm-agents/wiki/Budget-Controls) |
32
+ | **Multi-Tenancy** | Per-tenant budgets, circuit breakers, and execution isolation | [Multi-Tenancy](https://github.com/adham90/ruby_llm-agents/wiki/Multi-Tenancy) |
30
33
  | **Workflows** | Pipelines, parallel execution, conditional routers | [Workflows](https://github.com/adham90/ruby_llm-agents/wiki/Workflows) |
31
34
  | **Dashboard** | Real-time Turbo-powered monitoring UI | [Dashboard](https://github.com/adham90/ruby_llm-agents/wiki/Dashboard) |
32
35
  | **Streaming** | Real-time response streaming with TTFT tracking | [Streaming](https://github.com/adham90/ruby_llm-agents/wiki/Streaming) |
@@ -116,13 +119,18 @@ See [Conversation History](https://github.com/adham90/ruby_llm-agents/wiki/Conve
116
119
 
117
120
  ## Documentation
118
121
 
122
+ > **Note:** Wiki content lives in the [`wiki/`](wiki/) folder. To sync changes to the [GitHub Wiki](https://github.com/adham90/ruby_llm-agents/wiki), run `./scripts/sync-wiki.sh`.
123
+
119
124
  | Guide | Description |
120
125
  |-------|-------------|
121
126
  | [Getting Started](https://github.com/adham90/ruby_llm-agents/wiki/Getting-Started) | Installation, configuration, first agent |
122
- | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) | All DSL options: model, temperature, params, caching |
123
- | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) | Retries, fallbacks, circuit breakers, timeouts |
127
+ | [Agent DSL](https://github.com/adham90/ruby_llm-agents/wiki/Agent-DSL) | All DSL options: model, temperature, params, caching, description |
128
+ | [Reliability](https://github.com/adham90/ruby_llm-agents/wiki/Reliability) | Retries, fallbacks, circuit breakers, timeouts, reliability block |
124
129
  | [Workflows](https://github.com/adham90/ruby_llm-agents/wiki/Workflows) | Pipelines, parallel execution, routers |
125
130
  | [Budget Controls](https://github.com/adham90/ruby_llm-agents/wiki/Budget-Controls) | Spending limits, alerts, enforcement |
131
+ | [Multi-Tenancy](https://github.com/adham90/ruby_llm-agents/wiki/Multi-Tenancy) | Per-tenant budgets, isolation, configuration |
132
+ | [Testing Agents](https://github.com/adham90/ruby_llm-agents/wiki/Testing-Agents) | RSpec patterns, mocking, dry_run mode |
133
+ | [Error Handling](https://github.com/adham90/ruby_llm-agents/wiki/Error-Handling) | Error types, recovery patterns |
126
134
  | [Dashboard](https://github.com/adham90/ruby_llm-agents/wiki/Dashboard) | Setup, authentication, analytics |
127
135
  | [Production](https://github.com/adham90/ruby_llm-agents/wiki/Production-Deployment) | Deployment best practices, background jobs |
128
136
  | [API Reference](https://github.com/adham90/ruby_llm-agents/wiki/API-Reference) | Complete class documentation |
@@ -135,19 +143,22 @@ Build resilient agents with built-in fault tolerance:
135
143
  ```ruby
136
144
  class ReliableAgent < ApplicationAgent
137
145
  model "gpt-4o"
146
+ description "A resilient agent with automatic retries and fallbacks"
138
147
 
139
- # Retry on failures with exponential backoff
148
+ # Option 1: Individual DSL methods
140
149
  retries max: 3, backoff: :exponential
141
-
142
- # Fall back to alternative models
143
150
  fallback_models "gpt-4o-mini", "claude-3-5-sonnet"
144
-
145
- # Prevent cascading failures
146
151
  circuit_breaker errors: 10, within: 60, cooldown: 300
147
-
148
- # Maximum time for all attempts
149
152
  total_timeout 30
150
153
 
154
+ # Option 2: Grouped reliability block (equivalent to above)
155
+ reliability do
156
+ retries max: 3, backoff: :exponential
157
+ fallback_models "gpt-4o-mini", "claude-3-5-sonnet"
158
+ circuit_breaker errors: 10, within: 60, cooldown: 300
159
+ total_timeout 30
160
+ end
161
+
151
162
  param :query, required: true
152
163
 
153
164
  def user_prompt
@@ -156,6 +167,28 @@ class ReliableAgent < ApplicationAgent
156
167
  end
157
168
  ```
158
169
 
170
+ ### Enhanced Result Object
171
+
172
+ The result object provides detailed execution metadata:
173
+
174
+ ```ruby
175
+ result = ReliableAgent.call(query: "test")
176
+
177
+ # Basic response
178
+ result.content # => { ... }
179
+ result.success? # => true
180
+
181
+ # Reliability info
182
+ result.attempts_count # => 2 (if retry occurred)
183
+ result.used_fallback? # => true (if fallback model used)
184
+ result.chosen_model_id # => "gpt-4o-mini" (actual model used)
185
+
186
+ # Cost & timing
187
+ result.total_cost # => 0.00025
188
+ result.total_tokens # => 150
189
+ result.duration_ms # => 850
190
+ ```
191
+
159
192
  ## Workflow Orchestration
160
193
 
161
194
  Compose agents into complex workflows:
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ # Controller for managing API configurations
6
+ #
7
+ # Provides CRUD operations for global and tenant-specific API
8
+ # configurations, including API keys and connection settings.
9
+ #
10
+ # @see ApiConfiguration
11
+ # @api private
12
+ class ApiConfigurationsController < ApplicationController
13
+ before_action :ensure_table_exists
14
+ before_action :set_global_config, only: [:show, :edit, :update]
15
+ before_action :set_tenant_config, only: [:tenant, :edit_tenant, :update_tenant]
16
+
17
+ # Displays the global API configuration
18
+ #
19
+ # @return [void]
20
+ def show
21
+ @resolved = ApiConfiguration.resolve
22
+ @provider_statuses = @resolved.provider_statuses_with_source
23
+ end
24
+
25
+ # Renders the edit form for global configuration
26
+ #
27
+ # @return [void]
28
+ def edit
29
+ @resolved = ApiConfiguration.resolve
30
+ end
31
+
32
+ # Updates the global API configuration
33
+ #
34
+ # @return [void]
35
+ def update
36
+ if @config.update(api_configuration_params)
37
+ log_configuration_change(@config, "global")
38
+ redirect_to edit_api_configuration_path, notice: "API configuration updated successfully"
39
+ else
40
+ render :edit, status: :unprocessable_entity
41
+ end
42
+ end
43
+
44
+ # Displays tenant-specific API configuration
45
+ #
46
+ # @return [void]
47
+ def tenant
48
+ @resolved = ApiConfiguration.resolve(tenant_id: params[:tenant_id])
49
+ @provider_statuses = @resolved.provider_statuses_with_source
50
+ @tenant_budget = TenantBudget.for_tenant(params[:tenant_id])
51
+ end
52
+
53
+ # Renders the edit form for tenant configuration
54
+ #
55
+ # @return [void]
56
+ def edit_tenant
57
+ @resolved = ApiConfiguration.resolve(tenant_id: @tenant_id)
58
+ end
59
+
60
+ # Updates a tenant-specific API configuration
61
+ #
62
+ # @return [void]
63
+ def update_tenant
64
+ if @config.update(api_configuration_params)
65
+ log_configuration_change(@config, "tenant:#{params[:tenant_id]}")
66
+ redirect_to edit_tenant_api_configuration_path(params[:tenant_id]),
67
+ notice: "Tenant API configuration updated successfully"
68
+ else
69
+ render :edit_tenant, status: :unprocessable_entity
70
+ end
71
+ end
72
+
73
+ # Tests API key validity for a specific provider
74
+ # (Optional - can be used for AJAX validation)
75
+ #
76
+ # @return [void]
77
+ def test_connection
78
+ provider = params[:provider]
79
+ api_key = params[:api_key]
80
+
81
+ result = test_provider_connection(provider, api_key)
82
+
83
+ render json: {
84
+ success: result[:success],
85
+ message: result[:message],
86
+ models: result[:models]
87
+ }
88
+ rescue StandardError => e
89
+ render json: { success: false, message: e.message }
90
+ end
91
+
92
+ private
93
+
94
+ # Ensures the api_configurations table exists
95
+ def ensure_table_exists
96
+ return if ApiConfiguration.table_exists?
97
+
98
+ flash[:alert] = "API configurations table not found. Run the generator: rails generate ruby_llm_agents:api_configuration"
99
+ redirect_to root_path
100
+ end
101
+
102
+ # Sets the global configuration (creates if not exists)
103
+ def set_global_config
104
+ @config = ApiConfiguration.global
105
+ end
106
+
107
+ # Sets the tenant-specific configuration (creates if not exists)
108
+ def set_tenant_config
109
+ @tenant_id = params[:tenant_id]
110
+ raise ActionController::RoutingError, "Tenant ID required" if @tenant_id.blank?
111
+
112
+ @config = ApiConfiguration.for_tenant!(@tenant_id)
113
+ end
114
+
115
+ # Strong parameters for API configuration
116
+ #
117
+ # @return [ActionController::Parameters]
118
+ def api_configuration_params
119
+ params.require(:api_configuration).permit(
120
+ # API Keys
121
+ :openai_api_key,
122
+ :anthropic_api_key,
123
+ :gemini_api_key,
124
+ :deepseek_api_key,
125
+ :mistral_api_key,
126
+ :perplexity_api_key,
127
+ :openrouter_api_key,
128
+ :gpustack_api_key,
129
+ :xai_api_key,
130
+ :ollama_api_key,
131
+ # AWS Bedrock
132
+ :bedrock_api_key,
133
+ :bedrock_secret_key,
134
+ :bedrock_session_token,
135
+ :bedrock_region,
136
+ # Vertex AI
137
+ :vertexai_credentials,
138
+ :vertexai_project_id,
139
+ :vertexai_location,
140
+ # Endpoints
141
+ :openai_api_base,
142
+ :gemini_api_base,
143
+ :ollama_api_base,
144
+ :gpustack_api_base,
145
+ :xai_api_base,
146
+ # OpenAI Options
147
+ :openai_organization_id,
148
+ :openai_project_id,
149
+ # Default Models
150
+ :default_model,
151
+ :default_embedding_model,
152
+ :default_image_model,
153
+ :default_moderation_model,
154
+ # Connection Settings
155
+ :request_timeout,
156
+ :max_retries,
157
+ :retry_interval,
158
+ :retry_backoff_factor,
159
+ :retry_interval_randomness,
160
+ :http_proxy,
161
+ # Inheritance
162
+ :inherit_global_defaults
163
+ ).tap do |permitted|
164
+ # Remove blank API keys to prevent overwriting with empty values
165
+ # This allows users to submit forms without touching existing keys
166
+ ApiConfiguration::API_KEY_ATTRIBUTES.each do |key|
167
+ permitted.delete(key) if permitted[key].blank?
168
+ end
169
+ end
170
+ end
171
+
172
+ # Logs configuration changes for audit purposes
173
+ #
174
+ # @param config [ApiConfiguration] The configuration that changed
175
+ # @param scope [String] The scope identifier
176
+ def log_configuration_change(config, scope)
177
+ changed_fields = config.previous_changes.keys.reject { |k| k.end_with?("_at") }
178
+ return if changed_fields.empty?
179
+
180
+ # Mask sensitive fields in the log
181
+ masked_changes = changed_fields.map do |field|
182
+ if field.include?("api_key") || field.include?("secret") || field.include?("credentials")
183
+ "#{field}: [REDACTED]"
184
+ else
185
+ "#{field}: #{config.previous_changes[field].last}"
186
+ end
187
+ end
188
+
189
+ Rails.logger.info(
190
+ "[RubyLLM::Agents] API configuration updated for #{scope}: #{masked_changes.join(', ')}"
191
+ )
192
+ end
193
+
194
+ # Tests connection to a specific provider
195
+ #
196
+ # @param provider [String] Provider key
197
+ # @param api_key [String] API key to test
198
+ # @return [Hash] Test result with success, message, and models
199
+ def test_provider_connection(provider, api_key)
200
+ # This is a placeholder - actual implementation would depend on
201
+ # RubyLLM's ability to list models or make a test request
202
+ case provider
203
+ when "openai"
204
+ # Example: Try to list models
205
+ { success: true, message: "Connection successful", models: [] }
206
+ when "anthropic"
207
+ { success: true, message: "Connection successful", models: [] }
208
+ else
209
+ { success: false, message: "Provider not supported for testing" }
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Agents
5
- # Controller for displaying global configuration settings
5
+ # Controller for displaying system configuration
6
6
  #
7
7
  # Shows all configuration options from RubyLLM::Agents.configuration
8
8
  # in a read-only dashboard view.
9
9
  #
10
10
  # @api private
11
- class SettingsController < ApplicationController
12
- # Displays the global configuration settings
11
+ class SystemConfigController < ApplicationController
12
+ # Displays the system configuration
13
13
  #
14
14
  # @return [void]
15
15
  def show
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ # Controller for managing tenant budgets
6
+ #
7
+ # Provides CRUD operations for viewing and editing tenant budget
8
+ # configurations, including cost limits and token limits.
9
+ #
10
+ # @see TenantBudget For budget configuration model
11
+ # @api private
12
+ class TenantsController < ApplicationController
13
+ # Lists all tenant budgets
14
+ #
15
+ # @return [void]
16
+ def index
17
+ @tenants = TenantBudget.order(:name, :tenant_id)
18
+ end
19
+
20
+ # Shows a single tenant's budget details
21
+ #
22
+ # @return [void]
23
+ def show
24
+ @tenant = TenantBudget.find(params[:id])
25
+ @executions = tenant_executions(@tenant.tenant_id).recent.limit(10)
26
+ @usage_stats = calculate_usage_stats(@tenant)
27
+ end
28
+
29
+ # Renders the edit form for a tenant budget
30
+ #
31
+ # @return [void]
32
+ def edit
33
+ @tenant = TenantBudget.find(params[:id])
34
+ end
35
+
36
+ # Updates a tenant budget
37
+ #
38
+ # @return [void]
39
+ def update
40
+ @tenant = TenantBudget.find(params[:id])
41
+ if @tenant.update(tenant_params)
42
+ redirect_to tenant_path(@tenant), notice: "Tenant updated successfully"
43
+ else
44
+ render :edit, status: :unprocessable_entity
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Strong parameters for tenant budget
51
+ #
52
+ # @return [ActionController::Parameters] Permitted parameters
53
+ def tenant_params
54
+ params.require(:tenant_budget).permit(
55
+ :name, :daily_limit, :monthly_limit,
56
+ :daily_token_limit, :monthly_token_limit,
57
+ :enforcement
58
+ )
59
+ end
60
+
61
+ # Returns executions scoped to a specific tenant
62
+ #
63
+ # @param tenant_id [String] The tenant identifier
64
+ # @return [ActiveRecord::Relation] Executions for the tenant
65
+ def tenant_executions(tenant_id)
66
+ Execution.by_tenant(tenant_id)
67
+ end
68
+
69
+ # Calculates usage statistics for a tenant
70
+ #
71
+ # @param tenant [TenantBudget] The tenant budget record
72
+ # @return [Hash] Usage statistics
73
+ def calculate_usage_stats(tenant)
74
+ scope = tenant_executions(tenant.tenant_id)
75
+ today_scope = scope.where("created_at >= ?", Time.current.beginning_of_day)
76
+ month_scope = scope.where("created_at >= ?", Time.current.beginning_of_month)
77
+
78
+ daily_spend = today_scope.sum(:total_cost) || 0
79
+ monthly_spend = month_scope.sum(:total_cost) || 0
80
+ daily_tokens = today_scope.sum(:total_tokens) || 0
81
+ monthly_tokens = month_scope.sum(:total_tokens) || 0
82
+
83
+ {
84
+ daily_spend: daily_spend,
85
+ monthly_spend: monthly_spend,
86
+ daily_tokens: daily_tokens,
87
+ monthly_tokens: monthly_tokens,
88
+ daily_spend_percentage: percentage_used(daily_spend, tenant.effective_daily_limit),
89
+ monthly_spend_percentage: percentage_used(monthly_spend, tenant.effective_monthly_limit),
90
+ daily_token_percentage: percentage_used(daily_tokens, tenant.effective_daily_token_limit),
91
+ monthly_token_percentage: percentage_used(monthly_tokens, tenant.effective_monthly_token_limit),
92
+ total_executions: scope.count,
93
+ total_cost: scope.sum(:total_cost) || 0,
94
+ total_tokens: scope.sum(:total_tokens) || 0
95
+ }
96
+ end
97
+
98
+ # Calculates percentage used
99
+ #
100
+ # @param current [Numeric] Current usage
101
+ # @param limit [Numeric, nil] The limit
102
+ # @return [Float] Percentage used (0-100+)
103
+ def percentage_used(current, limit)
104
+ return 0 if limit.nil? || limit.to_f <= 0
105
+ (current.to_f / limit.to_f * 100).round(1)
106
+ end
107
+ end
108
+ end
109
+ end