ruby_llm-agents 0.2.4 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +413 -0
- data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
- data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
- data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
- data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
- data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
- data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
- data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
- data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
- data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
- data/app/models/ruby_llm/agents/execution.rb +259 -16
- data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
- data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
- data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
- data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
- data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
- data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
- data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
- data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
- data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
- data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
- data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
- data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
- data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
- data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
- data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
- data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
- data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
- data/config/routes.rb +7 -0
- data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
- data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +143 -8
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
- data/lib/ruby_llm/agents/alert_manager.rb +207 -0
- data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
- data/lib/ruby_llm/agents/base.rb +597 -112
- data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
- data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
- data/lib/ruby_llm/agents/configuration.rb +279 -1
- data/lib/ruby_llm/agents/engine.rb +58 -6
- data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
- data/lib/ruby_llm/agents/inflections.rb +13 -2
- data/lib/ruby_llm/agents/instrumentation.rb +538 -87
- data/lib/ruby_llm/agents/redactor.rb +130 -0
- data/lib/ruby_llm/agents/reliability.rb +185 -0
- data/lib/ruby_llm/agents/version.rb +3 -1
- data/lib/ruby_llm/agents.rb +52 -0
- metadata +41 -2
- data/app/controllers/ruby_llm/agents/application_controller.rb +0 -37
|
@@ -2,7 +2,182 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Agents
|
|
5
|
+
# Global configuration for RubyLLM::Agents
|
|
6
|
+
#
|
|
7
|
+
# Provides centralized settings for agent behavior, dashboard authentication,
|
|
8
|
+
# caching, and observability thresholds.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic configuration
|
|
11
|
+
# RubyLLM::Agents.configure do |config|
|
|
12
|
+
# config.default_model = "gpt-4o"
|
|
13
|
+
# config.default_temperature = 0.7
|
|
14
|
+
# config.async_logging = true
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Dashboard with HTTP Basic Auth
|
|
18
|
+
# RubyLLM::Agents.configure do |config|
|
|
19
|
+
# config.basic_auth_username = ENV["AGENTS_USER"]
|
|
20
|
+
# config.basic_auth_password = ENV["AGENTS_PASS"]
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Dashboard with custom authentication
|
|
24
|
+
# RubyLLM::Agents.configure do |config|
|
|
25
|
+
# config.dashboard_parent_controller = "AdminController"
|
|
26
|
+
# config.dashboard_auth = ->(controller) { controller.current_user&.admin? }
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @see RubyLLM::Agents.configure
|
|
30
|
+
# @api public
|
|
5
31
|
class Configuration
|
|
32
|
+
# @!attribute [rw] default_model
|
|
33
|
+
# The default LLM model identifier for all agents.
|
|
34
|
+
# Can be overridden per-agent using the `model` DSL method.
|
|
35
|
+
# @return [String] Model identifier (default: "gemini-2.0-flash")
|
|
36
|
+
# @example
|
|
37
|
+
# config.default_model = "gpt-4o"
|
|
38
|
+
|
|
39
|
+
# @!attribute [rw] default_temperature
|
|
40
|
+
# The default temperature for LLM responses (0.0 to 2.0).
|
|
41
|
+
# Lower values produce more deterministic outputs.
|
|
42
|
+
# @return [Float] Temperature value (default: 0.0)
|
|
43
|
+
|
|
44
|
+
# @!attribute [rw] default_timeout
|
|
45
|
+
# Maximum seconds to wait for an LLM response before timing out.
|
|
46
|
+
# @return [Integer] Timeout in seconds (default: 60)
|
|
47
|
+
|
|
48
|
+
# @!attribute [rw] async_logging
|
|
49
|
+
# Whether to log executions via background job (recommended for production).
|
|
50
|
+
# When false, executions are logged synchronously.
|
|
51
|
+
# @return [Boolean] Enable async logging (default: true)
|
|
52
|
+
|
|
53
|
+
# @!attribute [rw] retention_period
|
|
54
|
+
# How long to retain execution records before cleanup.
|
|
55
|
+
# @return [ActiveSupport::Duration] Retention period (default: 30.days)
|
|
56
|
+
|
|
57
|
+
# @!attribute [rw] anomaly_cost_threshold
|
|
58
|
+
# Cost threshold in dollars that triggers anomaly logging.
|
|
59
|
+
# Executions exceeding this cost are logged as warnings.
|
|
60
|
+
# @return [Float] Cost threshold in USD (default: 5.00)
|
|
61
|
+
|
|
62
|
+
# @!attribute [rw] anomaly_duration_threshold
|
|
63
|
+
# Duration threshold in milliseconds that triggers anomaly logging.
|
|
64
|
+
# @return [Integer] Duration threshold in ms (default: 10_000)
|
|
65
|
+
|
|
66
|
+
# @!attribute [rw] dashboard_auth
|
|
67
|
+
# Lambda for custom dashboard authentication.
|
|
68
|
+
# Receives the controller instance, should return truthy to allow access.
|
|
69
|
+
# @return [Proc] Authentication lambda (default: allows all)
|
|
70
|
+
# @example
|
|
71
|
+
# config.dashboard_auth = ->(c) { c.current_user&.admin? }
|
|
72
|
+
|
|
73
|
+
# @!attribute [rw] dashboard_parent_controller
|
|
74
|
+
# Parent controller class name for the dashboard.
|
|
75
|
+
# Use this to inherit authentication from your app's admin controller.
|
|
76
|
+
# @return [String] Controller class name (default: "ActionController::Base")
|
|
77
|
+
|
|
78
|
+
# @!attribute [rw] basic_auth_username
|
|
79
|
+
# Username for HTTP Basic Auth on the dashboard.
|
|
80
|
+
# Both username and password must be set to enable Basic Auth.
|
|
81
|
+
# @return [String, nil] Username or nil to disable (default: nil)
|
|
82
|
+
|
|
83
|
+
# @!attribute [rw] basic_auth_password
|
|
84
|
+
# Password for HTTP Basic Auth on the dashboard.
|
|
85
|
+
# @return [String, nil] Password or nil to disable (default: nil)
|
|
86
|
+
|
|
87
|
+
# @!attribute [rw] per_page
|
|
88
|
+
# Number of records per page in dashboard listings.
|
|
89
|
+
# @return [Integer] Records per page (default: 25)
|
|
90
|
+
|
|
91
|
+
# @!attribute [rw] recent_executions_limit
|
|
92
|
+
# Number of recent executions shown on the dashboard home.
|
|
93
|
+
# @return [Integer] Limit for recent executions (default: 10)
|
|
94
|
+
|
|
95
|
+
# @!attribute [rw] job_retry_attempts
|
|
96
|
+
# Number of retry attempts for the async logging job on failure.
|
|
97
|
+
# @return [Integer] Retry attempts (default: 3)
|
|
98
|
+
|
|
99
|
+
# @!attribute [w] cache_store
|
|
100
|
+
# Custom cache store for agent response caching.
|
|
101
|
+
# Falls back to Rails.cache if not set.
|
|
102
|
+
# @return [ActiveSupport::Cache::Store, nil]
|
|
103
|
+
|
|
104
|
+
# @!attribute [rw] default_retries
|
|
105
|
+
# Default retry configuration for all agents.
|
|
106
|
+
# Can be overridden per-agent using the `retries` DSL method.
|
|
107
|
+
# @return [Hash] Retry config with :max, :backoff, :base, :max_delay, :on keys
|
|
108
|
+
# @example
|
|
109
|
+
# config.default_retries = { max: 2, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [] }
|
|
110
|
+
|
|
111
|
+
# @!attribute [rw] default_fallback_models
|
|
112
|
+
# Default fallback models for all agents.
|
|
113
|
+
# Can be overridden per-agent using the `fallback_models` DSL method.
|
|
114
|
+
# @return [Array<String>] List of model identifiers to try on failure
|
|
115
|
+
|
|
116
|
+
# @!attribute [rw] default_total_timeout
|
|
117
|
+
# Default total timeout across all retry attempts.
|
|
118
|
+
# Can be overridden per-agent using the `total_timeout` DSL method.
|
|
119
|
+
# @return [Integer, nil] Total timeout in seconds, or nil for no limit
|
|
120
|
+
|
|
121
|
+
# @!attribute [rw] default_streaming
|
|
122
|
+
# Whether streaming mode is enabled by default for all agents.
|
|
123
|
+
# When enabled and a block is passed to call, chunks are yielded as they arrive.
|
|
124
|
+
# Can be overridden per-agent using the `streaming` DSL method.
|
|
125
|
+
# @return [Boolean] Enable streaming (default: false)
|
|
126
|
+
# @example
|
|
127
|
+
# config.default_streaming = true
|
|
128
|
+
|
|
129
|
+
# @!attribute [rw] default_tools
|
|
130
|
+
# Default tools available to all agents.
|
|
131
|
+
# Should be an array of RubyLLM::Tool classes.
|
|
132
|
+
# Can be overridden or extended per-agent using the `tools` DSL method.
|
|
133
|
+
# @return [Array<Class>] Tool classes (default: [])
|
|
134
|
+
# @example
|
|
135
|
+
# config.default_tools = [WeatherTool, SearchTool]
|
|
136
|
+
|
|
137
|
+
# @!attribute [rw] budgets
|
|
138
|
+
# Budget configuration for cost governance.
|
|
139
|
+
# @return [Hash, nil] Budget config with :global_daily, :global_monthly, :per_agent_daily, :per_agent_monthly, :enforcement keys
|
|
140
|
+
# @example
|
|
141
|
+
# config.budgets = {
|
|
142
|
+
# global_daily: 25.0,
|
|
143
|
+
# global_monthly: 300.0,
|
|
144
|
+
# per_agent_daily: { "ContentAgent" => 5.0 },
|
|
145
|
+
# per_agent_monthly: { "ContentAgent" => 120.0 },
|
|
146
|
+
# enforcement: :soft
|
|
147
|
+
# }
|
|
148
|
+
|
|
149
|
+
# @!attribute [rw] alerts
|
|
150
|
+
# Alert configuration for notifications.
|
|
151
|
+
# @return [Hash, nil] Alert config with :slack_webhook_url, :webhook_url, :on_events, :custom keys
|
|
152
|
+
# @example
|
|
153
|
+
# config.alerts = {
|
|
154
|
+
# slack_webhook_url: ENV["SLACK_WEBHOOK"],
|
|
155
|
+
# webhook_url: ENV["AGENTS_WEBHOOK"],
|
|
156
|
+
# on_events: [:budget_soft_cap, :budget_hard_cap, :breaker_open],
|
|
157
|
+
# custom: ->(event, payload) { Rails.logger.info("Alert: #{event}") }
|
|
158
|
+
# }
|
|
159
|
+
|
|
160
|
+
# @!attribute [rw] persist_prompts
|
|
161
|
+
# Whether to persist system and user prompts in execution records.
|
|
162
|
+
# Set to false to reduce storage or for privacy compliance.
|
|
163
|
+
# @return [Boolean] Enable prompt persistence (default: true)
|
|
164
|
+
|
|
165
|
+
# @!attribute [rw] persist_responses
|
|
166
|
+
# Whether to persist LLM responses in execution records.
|
|
167
|
+
# Set to false to reduce storage or for privacy compliance.
|
|
168
|
+
# @return [Boolean] Enable response persistence (default: true)
|
|
169
|
+
|
|
170
|
+
# @!attribute [rw] redaction
|
|
171
|
+
# Redaction configuration for PII and sensitive data.
|
|
172
|
+
# @return [Hash, nil] Redaction config with :fields, :patterns, :placeholder, :max_value_length keys
|
|
173
|
+
# @example
|
|
174
|
+
# config.redaction = {
|
|
175
|
+
# fields: %w[password api_key email ssn],
|
|
176
|
+
# patterns: [/\b\d{3}-\d{2}-\d{4}\b/],
|
|
177
|
+
# placeholder: "[REDACTED]",
|
|
178
|
+
# max_value_length: 5000
|
|
179
|
+
# }
|
|
180
|
+
|
|
6
181
|
attr_accessor :default_model,
|
|
7
182
|
:default_temperature,
|
|
8
183
|
:default_timeout,
|
|
@@ -13,10 +188,27 @@ module RubyLLM
|
|
|
13
188
|
:dashboard_auth,
|
|
14
189
|
:dashboard_parent_controller,
|
|
15
190
|
:basic_auth_username,
|
|
16
|
-
:basic_auth_password
|
|
191
|
+
:basic_auth_password,
|
|
192
|
+
:per_page,
|
|
193
|
+
:recent_executions_limit,
|
|
194
|
+
:job_retry_attempts,
|
|
195
|
+
:default_retries,
|
|
196
|
+
:default_fallback_models,
|
|
197
|
+
:default_total_timeout,
|
|
198
|
+
:default_streaming,
|
|
199
|
+
:default_tools,
|
|
200
|
+
:budgets,
|
|
201
|
+
:alerts,
|
|
202
|
+
:persist_prompts,
|
|
203
|
+
:persist_responses,
|
|
204
|
+
:redaction
|
|
17
205
|
|
|
18
206
|
attr_writer :cache_store
|
|
19
207
|
|
|
208
|
+
# Initializes configuration with default values
|
|
209
|
+
#
|
|
210
|
+
# @return [Configuration] A new configuration instance with defaults
|
|
211
|
+
# @api private
|
|
20
212
|
def initialize
|
|
21
213
|
@default_model = "gemini-2.0-flash"
|
|
22
214
|
@default_temperature = 0.0
|
|
@@ -30,11 +222,97 @@ module RubyLLM
|
|
|
30
222
|
@dashboard_parent_controller = "ActionController::Base"
|
|
31
223
|
@basic_auth_username = nil
|
|
32
224
|
@basic_auth_password = nil
|
|
225
|
+
@per_page = 25
|
|
226
|
+
@recent_executions_limit = 10
|
|
227
|
+
@job_retry_attempts = 3
|
|
228
|
+
|
|
229
|
+
# Reliability defaults (all disabled by default for backward compatibility)
|
|
230
|
+
@default_retries = { max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [] }
|
|
231
|
+
@default_fallback_models = []
|
|
232
|
+
@default_total_timeout = nil
|
|
233
|
+
|
|
234
|
+
# Streaming and tools defaults
|
|
235
|
+
@default_streaming = false
|
|
236
|
+
@default_tools = []
|
|
237
|
+
|
|
238
|
+
# Governance defaults
|
|
239
|
+
@budgets = nil
|
|
240
|
+
@alerts = nil
|
|
241
|
+
@persist_prompts = true
|
|
242
|
+
@persist_responses = true
|
|
243
|
+
@redaction = nil
|
|
33
244
|
end
|
|
34
245
|
|
|
246
|
+
# Returns the configured cache store, falling back to Rails.cache
|
|
247
|
+
#
|
|
248
|
+
# @return [ActiveSupport::Cache::Store] The cache store instance
|
|
249
|
+
# @example Using a custom cache store
|
|
250
|
+
# config.cache_store = ActiveSupport::Cache::MemoryStore.new
|
|
35
251
|
def cache_store
|
|
36
252
|
@cache_store || Rails.cache
|
|
37
253
|
end
|
|
254
|
+
|
|
255
|
+
# Returns whether budgets are configured and enforcement is enabled
|
|
256
|
+
#
|
|
257
|
+
# @return [Boolean] true if budgets are configured with enforcement
|
|
258
|
+
def budgets_enabled?
|
|
259
|
+
budgets.is_a?(Hash) && budgets[:enforcement] && budgets[:enforcement] != :none
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Returns the budget enforcement mode
|
|
263
|
+
#
|
|
264
|
+
# @return [Symbol] :none, :soft, or :hard
|
|
265
|
+
def budget_enforcement
|
|
266
|
+
budgets&.dig(:enforcement) || :none
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Returns whether alerts are configured
|
|
270
|
+
#
|
|
271
|
+
# @return [Boolean] true if any alert destination is configured
|
|
272
|
+
def alerts_enabled?
|
|
273
|
+
return false unless alerts.is_a?(Hash)
|
|
274
|
+
|
|
275
|
+
alerts[:slack_webhook_url].present? ||
|
|
276
|
+
alerts[:webhook_url].present? ||
|
|
277
|
+
alerts[:custom].present?
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Returns the list of events to alert on
|
|
281
|
+
#
|
|
282
|
+
# @return [Array<Symbol>] Event names to trigger alerts
|
|
283
|
+
def alert_events
|
|
284
|
+
alerts&.dig(:on_events) || []
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Returns merged redaction fields (default sensitive keys + configured)
|
|
288
|
+
#
|
|
289
|
+
# @return [Array<String>] Field names to redact
|
|
290
|
+
def redaction_fields
|
|
291
|
+
default_fields = %w[password token api_key secret credential auth key access_token]
|
|
292
|
+
configured_fields = redaction&.dig(:fields) || []
|
|
293
|
+
(default_fields + configured_fields).map(&:downcase).uniq
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Returns redaction patterns
|
|
297
|
+
#
|
|
298
|
+
# @return [Array<Regexp>] Patterns to match and redact
|
|
299
|
+
def redaction_patterns
|
|
300
|
+
redaction&.dig(:patterns) || []
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Returns the redaction placeholder string
|
|
304
|
+
#
|
|
305
|
+
# @return [String] Placeholder to replace redacted values
|
|
306
|
+
def redaction_placeholder
|
|
307
|
+
redaction&.dig(:placeholder) || "[REDACTED]"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Returns the maximum value length before truncation
|
|
311
|
+
#
|
|
312
|
+
# @return [Integer, nil] Max length, or nil for no limit
|
|
313
|
+
def redaction_max_value_length
|
|
314
|
+
redaction&.dig(:max_value_length)
|
|
315
|
+
end
|
|
38
316
|
end
|
|
39
317
|
end
|
|
40
318
|
end
|
|
@@ -2,22 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Agents
|
|
5
|
+
# Rails Engine for RubyLLM::Agents
|
|
6
|
+
#
|
|
7
|
+
# Provides a mountable dashboard for monitoring agent executions,
|
|
8
|
+
# with configurable authentication and automatic agent autoloading.
|
|
9
|
+
#
|
|
10
|
+
# @example Mounting the engine in routes.rb
|
|
11
|
+
# Rails.application.routes.draw do
|
|
12
|
+
# mount RubyLLM::Agents::Engine => "/agents"
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @example With authentication via parent controller
|
|
16
|
+
# RubyLLM::Agents.configure do |config|
|
|
17
|
+
# config.dashboard_parent_controller = "AdminController"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @see RubyLLM::Agents::Configuration
|
|
21
|
+
# @api public
|
|
5
22
|
class Engine < ::Rails::Engine
|
|
6
23
|
isolate_namespace RubyLLM::Agents
|
|
7
24
|
|
|
8
|
-
#
|
|
9
|
-
# This
|
|
25
|
+
# Dynamically creates the ApplicationController after Rails autoloading is ready.
|
|
26
|
+
# This allows the parent controller to be configured at runtime.
|
|
27
|
+
#
|
|
28
|
+
# The generated controller:
|
|
29
|
+
# - Inherits from the configured dashboard_parent_controller
|
|
30
|
+
# - Uses the engine's layout and helpers
|
|
31
|
+
# - Applies authentication via before_action
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
10
34
|
config.to_prepare do
|
|
11
35
|
require_relative "execution_logger_job"
|
|
12
36
|
require_relative "instrumentation"
|
|
13
37
|
require_relative "base"
|
|
14
38
|
|
|
15
|
-
#
|
|
39
|
+
# Resolve the parent controller class from configuration
|
|
40
|
+
# Default is ActionController::Base, but can be set to inherit from app controllers
|
|
16
41
|
parent_class = RubyLLM::Agents.configuration.dashboard_parent_controller.constantize
|
|
17
42
|
|
|
18
|
-
# Remove existing constant
|
|
43
|
+
# Remove existing constant to allow redefinition on configuration change
|
|
44
|
+
# This is necessary for Rails reloading in development
|
|
19
45
|
RubyLLM::Agents.send(:remove_const, :ApplicationController) if RubyLLM::Agents.const_defined?(:ApplicationController, false)
|
|
20
46
|
|
|
47
|
+
# Define the ApplicationController dynamically with the configured parent
|
|
21
48
|
RubyLLM::Agents.const_set(:ApplicationController, Class.new(parent_class) do
|
|
22
49
|
layout "rubyllm/agents/application"
|
|
23
50
|
helper RubyLLM::Agents::ApplicationHelper
|
|
@@ -25,6 +52,14 @@ module RubyLLM
|
|
|
25
52
|
|
|
26
53
|
private
|
|
27
54
|
|
|
55
|
+
# Authenticates dashboard access using configured method
|
|
56
|
+
#
|
|
57
|
+
# Authentication priority:
|
|
58
|
+
# 1. HTTP Basic Auth (if username/password configured)
|
|
59
|
+
# 2. Custom auth proc (dashboard_auth lambda)
|
|
60
|
+
#
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @api private
|
|
28
63
|
def authenticate_dashboard!
|
|
29
64
|
if basic_auth_configured?
|
|
30
65
|
authenticate_with_http_basic_auth
|
|
@@ -36,11 +71,21 @@ module RubyLLM
|
|
|
36
71
|
end
|
|
37
72
|
end
|
|
38
73
|
|
|
74
|
+
# Checks if HTTP Basic Auth credentials are configured
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] true if both username and password are present
|
|
77
|
+
# @api private
|
|
39
78
|
def basic_auth_configured?
|
|
40
79
|
config = RubyLLM::Agents.configuration
|
|
41
80
|
config.basic_auth_username.present? && config.basic_auth_password.present?
|
|
42
81
|
end
|
|
43
82
|
|
|
83
|
+
# Performs HTTP Basic Auth with timing-safe comparison
|
|
84
|
+
#
|
|
85
|
+
# Uses secure_compare to prevent timing attacks on credentials.
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
# @api private
|
|
44
89
|
def authenticate_with_http_basic_auth
|
|
45
90
|
config = RubyLLM::Agents.configuration
|
|
46
91
|
authenticate_or_request_with_http_basic("RubyLLM Agents") do |username, password|
|
|
@@ -51,14 +96,21 @@ module RubyLLM
|
|
|
51
96
|
end)
|
|
52
97
|
end
|
|
53
98
|
|
|
54
|
-
#
|
|
99
|
+
# Configures default generators for the engine
|
|
100
|
+
# Sets up RSpec and FactoryBot for generated specs
|
|
101
|
+
# @api private
|
|
55
102
|
config.generators do |g|
|
|
56
103
|
g.test_framework :rspec
|
|
57
104
|
g.fixture_replacement :factory_bot
|
|
58
105
|
g.factory_bot dir: "spec/factories"
|
|
59
106
|
end
|
|
60
107
|
|
|
61
|
-
#
|
|
108
|
+
# Adds the host app's app/agents directory to Rails autoload paths
|
|
109
|
+
#
|
|
110
|
+
# This allows agent classes defined in app/agents/ to be automatically
|
|
111
|
+
# loaded without explicit requires. Must run before set_autoload_paths.
|
|
112
|
+
#
|
|
113
|
+
# @api private
|
|
62
114
|
initializer "ruby_llm_agents.autoload_agents", before: :set_autoload_paths do |app|
|
|
63
115
|
agents_path = app.root.join("app/agents")
|
|
64
116
|
if agents_path.exist?
|
|
@@ -4,23 +4,26 @@ module RubyLLM
|
|
|
4
4
|
module Agents
|
|
5
5
|
# Background job for logging agent executions to the database
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# - Calculate costs based on token usage
|
|
10
|
-
# - Log anomalies (expensive, slow, or failed executions)
|
|
7
|
+
# Called automatically after each agent execution to create records,
|
|
8
|
+
# calculate costs, and detect anomalies.
|
|
11
9
|
#
|
|
12
|
-
# Configuration
|
|
10
|
+
# @example Configuration
|
|
13
11
|
# RubyLLM::Agents.configure do |config|
|
|
14
12
|
# config.anomaly_cost_threshold = 5.00 # Log if cost > $5
|
|
15
13
|
# config.anomaly_duration_threshold = 10_000 # Log if duration > 10s
|
|
16
14
|
# end
|
|
17
15
|
#
|
|
16
|
+
# @see RubyLLM::Agents::Instrumentation
|
|
17
|
+
# @api private
|
|
18
18
|
class ExecutionLoggerJob < ActiveJob::Base
|
|
19
19
|
queue_as :default
|
|
20
20
|
|
|
21
|
-
# Retry with polynomial backoff
|
|
22
21
|
retry_on StandardError, wait: :polynomially_longer, attempts: 3
|
|
23
22
|
|
|
23
|
+
# Creates execution record and performs post-processing
|
|
24
|
+
#
|
|
25
|
+
# @param execution_data [Hash] Execution attributes from instrumentation
|
|
26
|
+
# @return [void]
|
|
24
27
|
def perform(execution_data)
|
|
25
28
|
execution = Execution.create!(execution_data)
|
|
26
29
|
|
|
@@ -36,6 +39,10 @@ module RubyLLM
|
|
|
36
39
|
|
|
37
40
|
private
|
|
38
41
|
|
|
42
|
+
# Checks if execution should be flagged as anomalous
|
|
43
|
+
#
|
|
44
|
+
# @param execution [Execution] The execution to check
|
|
45
|
+
# @return [Boolean] true if cost/duration exceeds thresholds or status is error
|
|
39
46
|
def anomaly?(execution)
|
|
40
47
|
config = RubyLLM::Agents.configuration
|
|
41
48
|
|
|
@@ -44,6 +51,10 @@ module RubyLLM
|
|
|
44
51
|
execution.status_error?
|
|
45
52
|
end
|
|
46
53
|
|
|
54
|
+
# Logs a warning about an anomalous execution
|
|
55
|
+
#
|
|
56
|
+
# @param execution [Execution] The anomalous execution
|
|
57
|
+
# @return [void]
|
|
47
58
|
def log_anomaly(execution)
|
|
48
59
|
Rails.logger.warn(
|
|
49
60
|
"[RubyLLM::Agents] Execution anomaly detected: " \
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Inflection configuration for Rails autoloading
|
|
4
|
+
#
|
|
5
|
+
# Configures Rails and Zeitwerk to properly handle the "LLM" acronym
|
|
6
|
+
# and the "ruby_llm" directory naming convention.
|
|
7
|
+
#
|
|
8
|
+
# This ensures:
|
|
9
|
+
# - "LLM" is recognized as an acronym (not "Llm")
|
|
10
|
+
# - "ruby_llm" directory maps to "RubyLLM" module
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
|
|
14
|
+
# Register "LLM" as an acronym for ActiveSupport inflector
|
|
4
15
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
5
16
|
inflect.acronym "LLM"
|
|
6
17
|
end
|
|
7
18
|
|
|
8
|
-
# Configure Zeitwerk
|
|
19
|
+
# Configure Zeitwerk to map directory names correctly
|
|
9
20
|
ActiveSupport.on_load(:before_configuration) do
|
|
10
21
|
Rails.autoloaders.each do |autoloader|
|
|
11
22
|
autoloader.inflector.inflect("ruby_llm" => "RubyLLM")
|