ruby_llm-agents 0.2.4 → 0.3.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +273 -0
  3. data/app/channels/ruby_llm/agents/executions_channel.rb +24 -1
  4. data/app/controllers/concerns/ruby_llm/agents/filterable.rb +81 -0
  5. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +51 -0
  6. data/app/controllers/ruby_llm/agents/agents_controller.rb +228 -59
  7. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +167 -12
  8. data/app/controllers/ruby_llm/agents/executions_controller.rb +189 -31
  9. data/app/controllers/ruby_llm/agents/settings_controller.rb +20 -0
  10. data/app/helpers/ruby_llm/agents/application_helper.rb +307 -7
  11. data/app/models/ruby_llm/agents/execution/analytics.rb +224 -20
  12. data/app/models/ruby_llm/agents/execution/metrics.rb +41 -25
  13. data/app/models/ruby_llm/agents/execution/scopes.rb +234 -14
  14. data/app/models/ruby_llm/agents/execution.rb +259 -16
  15. data/app/services/ruby_llm/agents/agent_registry.rb +49 -12
  16. data/app/views/layouts/rubyllm/agents/application.html.erb +351 -85
  17. data/app/views/rubyllm/agents/agents/_version_comparison.html.erb +186 -0
  18. data/app/views/rubyllm/agents/agents/show.html.erb +233 -10
  19. data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +62 -0
  20. data/app/views/rubyllm/agents/dashboard/_alerts_feed.html.erb +62 -0
  21. data/app/views/rubyllm/agents/dashboard/_breaker_strip.html.erb +47 -0
  22. data/app/views/rubyllm/agents/dashboard/_budgets_bar.html.erb +165 -0
  23. data/app/views/rubyllm/agents/dashboard/_now_strip.html.erb +10 -0
  24. data/app/views/rubyllm/agents/dashboard/_now_strip_values.html.erb +71 -0
  25. data/app/views/rubyllm/agents/dashboard/index.html.erb +215 -109
  26. data/app/views/rubyllm/agents/executions/_filters.html.erb +152 -155
  27. data/app/views/rubyllm/agents/executions/_list.html.erb +103 -12
  28. data/app/views/rubyllm/agents/executions/dry_run.html.erb +149 -0
  29. data/app/views/rubyllm/agents/executions/index.html.erb +17 -72
  30. data/app/views/rubyllm/agents/executions/index.turbo_stream.erb +16 -2
  31. data/app/views/rubyllm/agents/executions/show.html.erb +693 -14
  32. data/app/views/rubyllm/agents/settings/show.html.erb +369 -0
  33. data/app/views/rubyllm/agents/shared/_filter_dropdown.html.erb +121 -0
  34. data/app/views/rubyllm/agents/shared/_select_dropdown.html.erb +85 -0
  35. data/config/routes.rb +7 -0
  36. data/lib/generators/ruby_llm_agents/templates/add_attempts_migration.rb.tt +27 -0
  37. data/lib/generators/ruby_llm_agents/templates/add_caching_migration.rb.tt +23 -0
  38. data/lib/generators/ruby_llm_agents/templates/add_finish_reason_migration.rb.tt +19 -0
  39. data/lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt +19 -0
  40. data/lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt +8 -0
  41. data/lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt +34 -0
  42. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +66 -4
  43. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +53 -6
  44. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +139 -8
  45. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +38 -1
  46. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +78 -0
  47. data/lib/ruby_llm/agents/alert_manager.rb +207 -0
  48. data/lib/ruby_llm/agents/attempt_tracker.rb +295 -0
  49. data/lib/ruby_llm/agents/base.rb +580 -112
  50. data/lib/ruby_llm/agents/budget_tracker.rb +360 -0
  51. data/lib/ruby_llm/agents/circuit_breaker.rb +197 -0
  52. data/lib/ruby_llm/agents/configuration.rb +279 -1
  53. data/lib/ruby_llm/agents/engine.rb +58 -6
  54. data/lib/ruby_llm/agents/execution_logger_job.rb +17 -6
  55. data/lib/ruby_llm/agents/inflections.rb +13 -2
  56. data/lib/ruby_llm/agents/instrumentation.rb +538 -87
  57. data/lib/ruby_llm/agents/redactor.rb +130 -0
  58. data/lib/ruby_llm/agents/reliability.rb +185 -0
  59. data/lib/ruby_llm/agents/version.rb +3 -1
  60. data/lib/ruby_llm/agents.rb +52 -0
  61. metadata +41 -2
  62. 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
- # Use to_prepare to load classes after autoloading is set up
9
- # This ensures app/models are available when referenced
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
- # Dynamically set parent controller based on configuration
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 if defined, then redefine with correct parent
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
- # Configure generators
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
- # Add app/agents to autoload paths for host app (must be done before initialization)
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
- # This job is called automatically after each agent execution to:
8
- # - Create an Execution record with all execution data
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
- # Configure acronym for Rails inflector (used in routes)
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 inflection when autoloaders are available
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")