ruby_llm-agents 0.5.0 → 1.0.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/README.md +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +172 -34
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -366
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
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
|
|
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_retryable_patterns
|
|
112
|
+
# Default patterns in error messages that indicate retryable errors.
|
|
113
|
+
# Organized by category for easy customization.
|
|
114
|
+
# @return [Hash<Symbol, Array<String>>] Categorized patterns
|
|
115
|
+
# @example
|
|
116
|
+
# config.default_retryable_patterns = {
|
|
117
|
+
# rate_limiting: ["rate limit", "429"],
|
|
118
|
+
# server_errors: ["500", "502", "503"],
|
|
119
|
+
# capacity: ["overloaded"]
|
|
120
|
+
# }
|
|
121
|
+
|
|
122
|
+
# @!attribute [rw] default_fallback_models
|
|
123
|
+
# Default fallback models for all agents.
|
|
124
|
+
# Can be overridden per-agent using the `fallback_models` DSL method.
|
|
125
|
+
# @return [Array<String>] List of model identifiers to try on failure
|
|
126
|
+
|
|
127
|
+
# @!attribute [rw] default_total_timeout
|
|
128
|
+
# Default total timeout across all retry attempts.
|
|
129
|
+
# Can be overridden per-agent using the `total_timeout` DSL method.
|
|
130
|
+
# @return [Integer, nil] Total timeout in seconds, or nil for no limit
|
|
131
|
+
|
|
132
|
+
# @!attribute [rw] default_streaming
|
|
133
|
+
# Whether streaming mode is enabled by default for all agents.
|
|
134
|
+
# When enabled and a block is passed to call, chunks are yielded as they arrive.
|
|
135
|
+
# Can be overridden per-agent using the `streaming` DSL method.
|
|
136
|
+
# @return [Boolean] Enable streaming (default: false)
|
|
137
|
+
# @example
|
|
138
|
+
# config.default_streaming = true
|
|
139
|
+
|
|
140
|
+
# @!attribute [rw] default_tools
|
|
141
|
+
# Default tools available to all agents.
|
|
142
|
+
# Should be an array of RubyLLM::Tool classes.
|
|
143
|
+
# Can be overridden or extended per-agent using the `tools` DSL method.
|
|
144
|
+
# @return [Array<Class>] Tool classes (default: [])
|
|
145
|
+
# @example
|
|
146
|
+
# config.default_tools = [WeatherTool, SearchTool]
|
|
147
|
+
|
|
148
|
+
# @!attribute [rw] default_thinking
|
|
149
|
+
# Default thinking/reasoning configuration for all agents.
|
|
150
|
+
# When set, enables extended thinking for supported models (Claude, Gemini, etc.).
|
|
151
|
+
# Can be overridden per-agent using the `thinking` DSL method.
|
|
152
|
+
# @return [Hash, nil] Thinking config with :effort and/or :budget keys (default: nil)
|
|
153
|
+
# @example Enable medium-effort thinking globally
|
|
154
|
+
# config.default_thinking = { effort: :medium }
|
|
155
|
+
# @example Enable high-effort thinking with budget
|
|
156
|
+
# config.default_thinking = { effort: :high, budget: 10000 }
|
|
157
|
+
|
|
158
|
+
# @!attribute [rw] budgets
|
|
159
|
+
# Budget configuration for cost governance.
|
|
160
|
+
# @return [Hash, nil] Budget config with :global_daily, :global_monthly, :per_agent_daily, :per_agent_monthly, :enforcement keys
|
|
161
|
+
# @example
|
|
162
|
+
# config.budgets = {
|
|
163
|
+
# global_daily: 25.0,
|
|
164
|
+
# global_monthly: 300.0,
|
|
165
|
+
# per_agent_daily: { "ContentAgent" => 5.0 },
|
|
166
|
+
# per_agent_monthly: { "ContentAgent" => 120.0 },
|
|
167
|
+
# enforcement: :soft
|
|
168
|
+
# }
|
|
169
|
+
|
|
170
|
+
# @!attribute [rw] alerts
|
|
171
|
+
# Alert configuration for notifications.
|
|
172
|
+
# @return [Hash, nil] Alert config with :slack_webhook_url, :webhook_url, :on_events, :custom keys
|
|
173
|
+
# @example
|
|
174
|
+
# config.alerts = {
|
|
175
|
+
# slack_webhook_url: ENV["SLACK_WEBHOOK"],
|
|
176
|
+
# webhook_url: ENV["AGENTS_WEBHOOK"],
|
|
177
|
+
# on_events: [:budget_soft_cap, :budget_hard_cap, :breaker_open],
|
|
178
|
+
# custom: ->(event, payload) { Rails.logger.info("Alert: #{event}") }
|
|
179
|
+
# }
|
|
180
|
+
|
|
181
|
+
# @!attribute [rw] persist_prompts
|
|
182
|
+
# Whether to persist system and user prompts in execution records.
|
|
183
|
+
# Set to false to reduce storage or for privacy compliance.
|
|
184
|
+
# @return [Boolean] Enable prompt persistence (default: true)
|
|
185
|
+
|
|
186
|
+
# @!attribute [rw] persist_responses
|
|
187
|
+
# Whether to persist LLM responses in execution records.
|
|
188
|
+
# Set to false to reduce storage or for privacy compliance.
|
|
189
|
+
# @return [Boolean] Enable response persistence (default: true)
|
|
190
|
+
|
|
191
|
+
# @!attribute [rw] redaction
|
|
192
|
+
# Redaction configuration for PII and sensitive data.
|
|
193
|
+
# @return [Hash, nil] Redaction config with :fields, :patterns, :placeholder, :max_value_length keys
|
|
194
|
+
# @example
|
|
195
|
+
# config.redaction = {
|
|
196
|
+
# fields: %w[password api_key email ssn],
|
|
197
|
+
# patterns: [/\b\d{3}-\d{2}-\d{4}\b/],
|
|
198
|
+
# placeholder: "[REDACTED]",
|
|
199
|
+
# max_value_length: 5000
|
|
200
|
+
# }
|
|
201
|
+
|
|
202
|
+
# @!attribute [rw] multi_tenancy_enabled
|
|
203
|
+
# Whether multi-tenancy features are enabled.
|
|
204
|
+
# When false, the gem behaves exactly as before (backward compatible).
|
|
205
|
+
# @return [Boolean] Enable multi-tenancy (default: false)
|
|
206
|
+
# @example
|
|
207
|
+
# config.multi_tenancy_enabled = true
|
|
208
|
+
|
|
209
|
+
# @!attribute [rw] tenant_resolver
|
|
210
|
+
# Lambda that returns the current tenant identifier.
|
|
211
|
+
# Called whenever tenant context is needed for budget tracking,
|
|
212
|
+
# circuit breakers, and execution recording.
|
|
213
|
+
# @return [Proc] Tenant resolution lambda (default: -> { nil })
|
|
214
|
+
# @example Using Rails CurrentAttributes
|
|
215
|
+
# config.tenant_resolver = -> { Current.tenant&.id }
|
|
216
|
+
# @example Using request store
|
|
217
|
+
# config.tenant_resolver = -> { RequestStore[:tenant_id] }
|
|
218
|
+
|
|
219
|
+
# @!attribute [rw] tenant_config_resolver
|
|
220
|
+
# Lambda that returns tenant configuration without querying the database.
|
|
221
|
+
# Called when resolving tenant budget config. If set, this takes priority
|
|
222
|
+
# over the TenantBudget database lookup.
|
|
223
|
+
# @return [Proc, nil] Tenant config resolver lambda (default: nil)
|
|
224
|
+
# @example Using an external tenant service
|
|
225
|
+
# config.tenant_config_resolver = ->(tenant_id) {
|
|
226
|
+
# tenant = Tenant.find(tenant_id)
|
|
227
|
+
# {
|
|
228
|
+
# name: tenant.name,
|
|
229
|
+
# daily_limit: tenant.subscription.daily_budget,
|
|
230
|
+
# monthly_limit: tenant.subscription.monthly_budget,
|
|
231
|
+
# daily_token_limit: tenant.subscription.daily_tokens,
|
|
232
|
+
# monthly_token_limit: tenant.subscription.monthly_tokens,
|
|
233
|
+
# enforcement: tenant.subscription.hard_limits? ? :hard : :soft
|
|
234
|
+
# }
|
|
235
|
+
# }
|
|
236
|
+
|
|
237
|
+
# @!attribute [rw] persist_messages_summary
|
|
238
|
+
# Whether to persist a summary of conversation messages in execution records.
|
|
239
|
+
# When true, stores message count and first/last messages (truncated).
|
|
240
|
+
# Set to false to disable message summary persistence.
|
|
241
|
+
# @return [Boolean] Enable messages summary persistence (default: true)
|
|
242
|
+
|
|
243
|
+
# @!attribute [rw] messages_summary_max_length
|
|
244
|
+
# Maximum character length for message content in the summary.
|
|
245
|
+
# Content exceeding this length will be truncated with "...".
|
|
246
|
+
# @return [Integer] Max length for message content (default: 500)
|
|
247
|
+
|
|
248
|
+
# @!attribute [rw] default_embedding_model
|
|
249
|
+
# The default embedding model identifier for all embedders.
|
|
250
|
+
# Can be overridden per-embedder using the `model` DSL method.
|
|
251
|
+
# @return [String] Model identifier (default: "text-embedding-3-small")
|
|
252
|
+
# @example
|
|
253
|
+
# config.default_embedding_model = "text-embedding-3-large"
|
|
254
|
+
|
|
255
|
+
# @!attribute [rw] default_embedding_dimensions
|
|
256
|
+
# The default vector dimensions for embeddings.
|
|
257
|
+
# Set to nil to use the model's default dimensions.
|
|
258
|
+
# Some models (like OpenAI text-embedding-3) support dimension reduction.
|
|
259
|
+
# @return [Integer, nil] Dimensions or nil for model default (default: nil)
|
|
260
|
+
# @example
|
|
261
|
+
# config.default_embedding_dimensions = 512
|
|
262
|
+
|
|
263
|
+
# @!attribute [rw] default_embedding_batch_size
|
|
264
|
+
# The default batch size for embedding operations.
|
|
265
|
+
# When embedding multiple texts, they are split into batches of this size.
|
|
266
|
+
# @return [Integer] Batch size (default: 100)
|
|
267
|
+
# @example
|
|
268
|
+
# config.default_embedding_batch_size = 50
|
|
269
|
+
|
|
270
|
+
# @!attribute [rw] track_embeddings
|
|
271
|
+
# Whether to track embedding executions in the database.
|
|
272
|
+
# When enabled, embedding operations are logged like agent executions.
|
|
273
|
+
# @return [Boolean] Enable embedding tracking (default: true)
|
|
274
|
+
# @example
|
|
275
|
+
# config.track_embeddings = false
|
|
276
|
+
|
|
277
|
+
# @!attribute [rw] default_moderation_model
|
|
278
|
+
# The default moderation model identifier for all agents.
|
|
279
|
+
# Can be overridden per-agent using the `moderation` DSL method.
|
|
280
|
+
# @return [String] Model identifier (default: "omni-moderation-latest")
|
|
281
|
+
# @example
|
|
282
|
+
# config.default_moderation_model = "text-moderation-007"
|
|
283
|
+
|
|
284
|
+
# @!attribute [rw] default_moderation_threshold
|
|
285
|
+
# The default threshold for moderation scores.
|
|
286
|
+
# Content with scores at or above this threshold will be flagged.
|
|
287
|
+
# Set to nil to use the provider's default flagging.
|
|
288
|
+
# @return [Float, nil] Threshold (0.0-1.0) or nil for provider default (default: nil)
|
|
289
|
+
# @example
|
|
290
|
+
# config.default_moderation_threshold = 0.8
|
|
291
|
+
|
|
292
|
+
# @!attribute [rw] default_moderation_action
|
|
293
|
+
# The default action when content is flagged.
|
|
294
|
+
# Can be overridden per-agent using the `moderation` DSL method.
|
|
295
|
+
# @return [Symbol] Action (:block, :raise, :warn, :log) (default: :block)
|
|
296
|
+
# @example
|
|
297
|
+
# config.default_moderation_action = :raise
|
|
298
|
+
|
|
299
|
+
# @!attribute [rw] track_moderation
|
|
300
|
+
# Whether to track moderation executions in the database.
|
|
301
|
+
# When enabled, moderation operations are logged as executions.
|
|
302
|
+
# @return [Boolean] Enable moderation tracking (default: true)
|
|
303
|
+
# @example
|
|
304
|
+
# config.track_moderation = false
|
|
305
|
+
|
|
306
|
+
# @!attribute [rw] default_transcription_model
|
|
307
|
+
# The default transcription model identifier for all transcribers.
|
|
308
|
+
# Can be overridden per-transcriber using the `model` DSL method.
|
|
309
|
+
# @return [String] Model identifier (default: "whisper-1")
|
|
310
|
+
# @example
|
|
311
|
+
# config.default_transcription_model = "gpt-4o-transcribe"
|
|
312
|
+
|
|
313
|
+
# @!attribute [rw] track_transcriptions
|
|
314
|
+
# Whether to track transcription executions in the database.
|
|
315
|
+
# When enabled, transcription operations are logged as executions.
|
|
316
|
+
# @return [Boolean] Enable transcription tracking (default: true)
|
|
317
|
+
# @example
|
|
318
|
+
# config.track_transcriptions = false
|
|
319
|
+
|
|
320
|
+
# @!attribute [rw] default_tts_provider
|
|
321
|
+
# The default TTS provider for all speakers.
|
|
322
|
+
# Can be overridden per-speaker using the `provider` DSL method.
|
|
323
|
+
# @return [Symbol] Provider (:openai, :elevenlabs, :google, :polly) (default: :openai)
|
|
324
|
+
# @example
|
|
325
|
+
# config.default_tts_provider = :elevenlabs
|
|
326
|
+
|
|
327
|
+
# @!attribute [rw] default_tts_model
|
|
328
|
+
# The default TTS model identifier for all speakers.
|
|
329
|
+
# Can be overridden per-speaker using the `model` DSL method.
|
|
330
|
+
# @return [String] Model identifier (default: "tts-1")
|
|
331
|
+
# @example
|
|
332
|
+
# config.default_tts_model = "tts-1-hd"
|
|
333
|
+
|
|
334
|
+
# @!attribute [rw] default_tts_voice
|
|
335
|
+
# The default voice for all speakers.
|
|
336
|
+
# Can be overridden per-speaker using the `voice` DSL method.
|
|
337
|
+
# @return [String] Voice name (default: "nova")
|
|
338
|
+
# @example
|
|
339
|
+
# config.default_tts_voice = "alloy"
|
|
340
|
+
|
|
341
|
+
# @!attribute [rw] track_speech
|
|
342
|
+
# Whether to track speech executions in the database.
|
|
343
|
+
# When enabled, speech operations are logged as executions.
|
|
344
|
+
# @return [Boolean] Enable speech tracking (default: true)
|
|
345
|
+
# @example
|
|
346
|
+
# config.track_speech = false
|
|
347
|
+
|
|
348
|
+
# @!attribute [rw] async_max_concurrency
|
|
349
|
+
# Maximum number of concurrent async operations when using batch processing.
|
|
350
|
+
# Controls the semaphore limit for Async::Semaphore.
|
|
351
|
+
# @return [Integer] Max concurrent operations (default: 10)
|
|
352
|
+
# @example
|
|
353
|
+
# config.async_max_concurrency = 20
|
|
354
|
+
|
|
355
|
+
# @!attribute [rw] root_directory
|
|
356
|
+
# The root directory name under app/ for all LLM components.
|
|
357
|
+
# This allows customization of the directory structure.
|
|
358
|
+
# @return [String] Directory name (default: "llm")
|
|
359
|
+
# @example
|
|
360
|
+
# config.root_directory = "ai" # Creates app/ai/ instead of app/llm/
|
|
361
|
+
|
|
362
|
+
# @!attribute [rw] root_namespace
|
|
363
|
+
# The root namespace for all LLM component classes.
|
|
364
|
+
# This should match the root_directory in camelized form.
|
|
365
|
+
# Set to nil or "" to use no namespace (classes at top-level).
|
|
366
|
+
# @return [String, nil] Namespace (default: "Llm")
|
|
367
|
+
# @example Custom namespace
|
|
368
|
+
# config.root_namespace = "AI" # Uses AI:: instead of Llm::
|
|
369
|
+
# @example No namespace (top-level classes)
|
|
370
|
+
# config.root_namespace = nil # Uses top-level classes (ApplicationAgent, etc.)
|
|
371
|
+
|
|
372
|
+
# Attributes without validation (simple accessors)
|
|
373
|
+
attr_accessor :default_model,
|
|
374
|
+
:async_logging,
|
|
375
|
+
:async_max_concurrency,
|
|
376
|
+
:retention_period,
|
|
377
|
+
:dashboard_parent_controller,
|
|
378
|
+
:basic_auth_username,
|
|
379
|
+
:basic_auth_password,
|
|
380
|
+
:default_fallback_models,
|
|
381
|
+
:default_total_timeout,
|
|
382
|
+
:default_streaming,
|
|
383
|
+
:default_tools,
|
|
384
|
+
:default_thinking,
|
|
385
|
+
:alerts,
|
|
386
|
+
:persist_prompts,
|
|
387
|
+
:persist_responses,
|
|
388
|
+
:redaction,
|
|
389
|
+
:multi_tenancy_enabled,
|
|
390
|
+
:persist_messages_summary,
|
|
391
|
+
:default_retryable_patterns,
|
|
392
|
+
:default_embedding_model,
|
|
393
|
+
:default_embedding_dimensions,
|
|
394
|
+
:default_embedding_batch_size,
|
|
395
|
+
:track_embeddings,
|
|
396
|
+
:default_moderation_model,
|
|
397
|
+
:default_moderation_threshold,
|
|
398
|
+
:default_moderation_action,
|
|
399
|
+
:track_moderation,
|
|
400
|
+
:default_transcription_model,
|
|
401
|
+
:track_transcriptions,
|
|
402
|
+
:default_tts_provider,
|
|
403
|
+
:default_tts_model,
|
|
404
|
+
:default_tts_voice,
|
|
405
|
+
:track_speech,
|
|
406
|
+
:track_executions,
|
|
407
|
+
:track_audio,
|
|
408
|
+
:track_cache_hits,
|
|
409
|
+
:default_image_model,
|
|
410
|
+
:default_image_size,
|
|
411
|
+
:default_image_quality,
|
|
412
|
+
:default_image_style,
|
|
413
|
+
:max_image_prompt_length,
|
|
414
|
+
:track_image_generation,
|
|
415
|
+
:image_model_aliases,
|
|
416
|
+
:litellm_pricing_url,
|
|
417
|
+
:litellm_pricing_cache_ttl,
|
|
418
|
+
:default_image_cost,
|
|
419
|
+
:image_model_pricing,
|
|
420
|
+
:default_variator_model,
|
|
421
|
+
:default_editor_model,
|
|
422
|
+
:default_transformer_model,
|
|
423
|
+
:default_upscaler_model,
|
|
424
|
+
:default_variation_strength,
|
|
425
|
+
:default_transform_strength,
|
|
426
|
+
:default_analyzer_model,
|
|
427
|
+
:default_analysis_type,
|
|
428
|
+
:default_analyzer_max_tags,
|
|
429
|
+
:default_background_remover_model,
|
|
430
|
+
:default_background_output_format,
|
|
431
|
+
:root_directory,
|
|
432
|
+
:root_namespace
|
|
433
|
+
|
|
434
|
+
# Attributes with validation (readers only, custom setters below)
|
|
435
|
+
attr_reader :default_temperature,
|
|
436
|
+
:default_timeout,
|
|
437
|
+
:anomaly_cost_threshold,
|
|
438
|
+
:anomaly_duration_threshold,
|
|
439
|
+
:per_page,
|
|
440
|
+
:recent_executions_limit,
|
|
441
|
+
:job_retry_attempts,
|
|
442
|
+
:messages_summary_max_length,
|
|
443
|
+
:dashboard_auth,
|
|
444
|
+
:tenant_resolver,
|
|
445
|
+
:tenant_config_resolver,
|
|
446
|
+
:default_retries,
|
|
447
|
+
:budgets
|
|
448
|
+
|
|
449
|
+
attr_writer :cache_store
|
|
450
|
+
|
|
451
|
+
# Sets default_temperature with validation
|
|
452
|
+
#
|
|
453
|
+
# @param value [Float] Temperature (0.0 to 2.0)
|
|
454
|
+
# @raise [ArgumentError] If value is outside valid range
|
|
455
|
+
def default_temperature=(value)
|
|
456
|
+
validate_range!(:default_temperature, value, 0.0, 2.0)
|
|
457
|
+
@default_temperature = value
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Sets default_timeout with validation
|
|
461
|
+
#
|
|
462
|
+
# @param value [Integer] Timeout in seconds (must be > 0)
|
|
463
|
+
# @raise [ArgumentError] If value is not positive
|
|
464
|
+
def default_timeout=(value)
|
|
465
|
+
validate_positive!(:default_timeout, value)
|
|
466
|
+
@default_timeout = value
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Sets anomaly_cost_threshold with validation
|
|
470
|
+
#
|
|
471
|
+
# @param value [Float] Cost threshold (must be >= 0)
|
|
472
|
+
# @raise [ArgumentError] If value is negative
|
|
473
|
+
def anomaly_cost_threshold=(value)
|
|
474
|
+
validate_non_negative!(:anomaly_cost_threshold, value)
|
|
475
|
+
@anomaly_cost_threshold = value
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Sets anomaly_duration_threshold with validation
|
|
479
|
+
#
|
|
480
|
+
# @param value [Integer] Duration threshold in ms (must be >= 0)
|
|
481
|
+
# @raise [ArgumentError] If value is negative
|
|
482
|
+
def anomaly_duration_threshold=(value)
|
|
483
|
+
validate_non_negative!(:anomaly_duration_threshold, value)
|
|
484
|
+
@anomaly_duration_threshold = value
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Sets per_page with validation
|
|
488
|
+
#
|
|
489
|
+
# @param value [Integer] Records per page (must be > 0)
|
|
490
|
+
# @raise [ArgumentError] If value is not positive
|
|
491
|
+
def per_page=(value)
|
|
492
|
+
validate_positive!(:per_page, value)
|
|
493
|
+
@per_page = value
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Sets recent_executions_limit with validation
|
|
497
|
+
#
|
|
498
|
+
# @param value [Integer] Limit (must be > 0)
|
|
499
|
+
# @raise [ArgumentError] If value is not positive
|
|
500
|
+
def recent_executions_limit=(value)
|
|
501
|
+
validate_positive!(:recent_executions_limit, value)
|
|
502
|
+
@recent_executions_limit = value
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Sets job_retry_attempts with validation
|
|
506
|
+
#
|
|
507
|
+
# @param value [Integer] Retry attempts (must be >= 0)
|
|
508
|
+
# @raise [ArgumentError] If value is negative
|
|
509
|
+
def job_retry_attempts=(value)
|
|
510
|
+
validate_non_negative!(:job_retry_attempts, value)
|
|
511
|
+
@job_retry_attempts = value
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
# Sets messages_summary_max_length with validation
|
|
515
|
+
#
|
|
516
|
+
# @param value [Integer] Max length (must be > 0)
|
|
517
|
+
# @raise [ArgumentError] If value is not positive
|
|
518
|
+
def messages_summary_max_length=(value)
|
|
519
|
+
validate_positive!(:messages_summary_max_length, value)
|
|
520
|
+
@messages_summary_max_length = value
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Sets dashboard_auth with validation
|
|
524
|
+
#
|
|
525
|
+
# @param value [Proc, nil] Authentication lambda or nil
|
|
526
|
+
# @raise [ArgumentError] If value is not callable or nil
|
|
527
|
+
def dashboard_auth=(value)
|
|
528
|
+
validate_callable!(:dashboard_auth, value, allow_nil: true)
|
|
529
|
+
@dashboard_auth = value
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Sets tenant_resolver with validation
|
|
533
|
+
#
|
|
534
|
+
# @param value [Proc] Tenant resolution lambda (must be callable)
|
|
535
|
+
# @raise [ArgumentError] If value is not callable
|
|
536
|
+
def tenant_resolver=(value)
|
|
537
|
+
validate_callable!(:tenant_resolver, value, allow_nil: false)
|
|
538
|
+
@tenant_resolver = value
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# Sets tenant_config_resolver with validation
|
|
542
|
+
#
|
|
543
|
+
# @param value [Proc, nil] Tenant config resolver lambda or nil
|
|
544
|
+
# @raise [ArgumentError] If value is not callable or nil
|
|
545
|
+
def tenant_config_resolver=(value)
|
|
546
|
+
validate_callable!(:tenant_config_resolver, value, allow_nil: true)
|
|
547
|
+
@tenant_config_resolver = value
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Sets default_retries with validation
|
|
551
|
+
#
|
|
552
|
+
# @param value [Hash] Retry configuration
|
|
553
|
+
# @raise [ArgumentError] If any values are invalid
|
|
554
|
+
def default_retries=(value)
|
|
555
|
+
validate_retries!(value)
|
|
556
|
+
@default_retries = value
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# Sets budgets with validation
|
|
560
|
+
#
|
|
561
|
+
# @param value [Hash, nil] Budget configuration
|
|
562
|
+
# @raise [ArgumentError] If enforcement is invalid
|
|
563
|
+
def budgets=(value)
|
|
564
|
+
validate_budgets!(value)
|
|
565
|
+
@budgets = value
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Sets default_embedding_batch_size with validation
|
|
569
|
+
#
|
|
570
|
+
# @param value [Integer] Batch size (must be > 0)
|
|
571
|
+
# @raise [ArgumentError] If value is not positive
|
|
572
|
+
def default_embedding_batch_size=(value)
|
|
573
|
+
validate_positive!(:default_embedding_batch_size, value)
|
|
574
|
+
@default_embedding_batch_size = value
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Sets default_embedding_dimensions with validation
|
|
578
|
+
#
|
|
579
|
+
# @param value [Integer, nil] Dimensions (must be nil or > 0)
|
|
580
|
+
# @raise [ArgumentError] If value is not nil or positive
|
|
581
|
+
def default_embedding_dimensions=(value)
|
|
582
|
+
unless value.nil? || (value.is_a?(Numeric) && value > 0)
|
|
583
|
+
raise ArgumentError, "default_embedding_dimensions must be nil or greater than 0"
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
@default_embedding_dimensions = value
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Initializes configuration with default values
|
|
590
|
+
#
|
|
591
|
+
# @return [Configuration] A new configuration instance with defaults
|
|
592
|
+
# @api private
|
|
593
|
+
def initialize
|
|
594
|
+
@default_model = "gemini-2.0-flash"
|
|
595
|
+
@default_temperature = 0.0
|
|
596
|
+
@default_timeout = 60
|
|
597
|
+
@cache_store = nil
|
|
598
|
+
@async_logging = true
|
|
599
|
+
@async_max_concurrency = 10
|
|
600
|
+
@retention_period = 30.days
|
|
601
|
+
@anomaly_cost_threshold = 5.00
|
|
602
|
+
@anomaly_duration_threshold = 10_000
|
|
603
|
+
@dashboard_auth = ->(_controller) { true }
|
|
604
|
+
@dashboard_parent_controller = "ActionController::Base"
|
|
605
|
+
@basic_auth_username = nil
|
|
606
|
+
@basic_auth_password = nil
|
|
607
|
+
@per_page = 25
|
|
608
|
+
@recent_executions_limit = 10
|
|
609
|
+
@job_retry_attempts = 3
|
|
610
|
+
|
|
611
|
+
# Reliability defaults (all disabled by default for backward compatibility)
|
|
612
|
+
@default_retries = { max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [] }
|
|
613
|
+
@default_fallback_models = []
|
|
614
|
+
@default_total_timeout = nil
|
|
615
|
+
@default_retryable_patterns = {
|
|
616
|
+
rate_limiting: ["rate limit", "rate_limit", "too many requests", "429"],
|
|
617
|
+
server_errors: ["500", "502", "503", "504", "service unavailable",
|
|
618
|
+
"internal server error", "bad gateway", "gateway timeout"],
|
|
619
|
+
capacity: ["overloaded", "capacity"]
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
# Streaming, tools, and thinking defaults
|
|
623
|
+
@default_streaming = false
|
|
624
|
+
@default_tools = []
|
|
625
|
+
@default_thinking = nil
|
|
626
|
+
|
|
627
|
+
# Governance defaults
|
|
628
|
+
@budgets = nil
|
|
629
|
+
@alerts = nil
|
|
630
|
+
@persist_prompts = true
|
|
631
|
+
@persist_responses = true
|
|
632
|
+
@redaction = nil
|
|
633
|
+
|
|
634
|
+
# Multi-tenancy defaults (disabled for backward compatibility)
|
|
635
|
+
@multi_tenancy_enabled = false
|
|
636
|
+
@tenant_resolver = -> { nil }
|
|
637
|
+
@tenant_config_resolver = nil
|
|
638
|
+
|
|
639
|
+
# Messages summary defaults
|
|
640
|
+
@persist_messages_summary = true
|
|
641
|
+
@messages_summary_max_length = 500
|
|
642
|
+
|
|
643
|
+
# Embedding defaults
|
|
644
|
+
@default_embedding_model = "text-embedding-3-small"
|
|
645
|
+
@default_embedding_dimensions = nil
|
|
646
|
+
@default_embedding_batch_size = 100
|
|
647
|
+
@track_embeddings = true
|
|
648
|
+
|
|
649
|
+
# Moderation defaults
|
|
650
|
+
@default_moderation_model = "omni-moderation-latest"
|
|
651
|
+
@default_moderation_threshold = nil
|
|
652
|
+
@default_moderation_action = :block
|
|
653
|
+
@track_moderation = true
|
|
654
|
+
|
|
655
|
+
# Transcription defaults
|
|
656
|
+
@default_transcription_model = "whisper-1"
|
|
657
|
+
@track_transcriptions = true
|
|
658
|
+
|
|
659
|
+
# TTS/Speech defaults
|
|
660
|
+
@default_tts_provider = :openai
|
|
661
|
+
@default_tts_model = "tts-1"
|
|
662
|
+
@default_tts_voice = "nova"
|
|
663
|
+
@track_speech = true
|
|
664
|
+
|
|
665
|
+
# Execution/conversation agent tracking
|
|
666
|
+
@track_executions = true
|
|
667
|
+
@track_audio = true
|
|
668
|
+
@track_cache_hits = true
|
|
669
|
+
|
|
670
|
+
# Image Generation defaults
|
|
671
|
+
@default_image_model = "gpt-image-1"
|
|
672
|
+
@default_image_size = "1024x1024"
|
|
673
|
+
@default_image_quality = "standard"
|
|
674
|
+
@default_image_style = "vivid"
|
|
675
|
+
@max_image_prompt_length = 4000
|
|
676
|
+
@track_image_generation = true
|
|
677
|
+
@image_model_aliases = {
|
|
678
|
+
fast: "flux-schnell",
|
|
679
|
+
quality: "gpt-image-1",
|
|
680
|
+
cheap: "sdxl"
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
# Image Pricing defaults
|
|
684
|
+
@litellm_pricing_url = nil # Use default from Pricing module
|
|
685
|
+
@litellm_pricing_cache_ttl = nil # Use default (24 hours)
|
|
686
|
+
@default_image_cost = 0.04 # Fallback cost per image
|
|
687
|
+
@image_model_pricing = {} # User-defined pricing overrides
|
|
688
|
+
|
|
689
|
+
# Phase 2: Image Variation, Editing, Transformation, Upscaling defaults
|
|
690
|
+
@default_variator_model = nil # Falls back to default_image_model
|
|
691
|
+
@default_editor_model = nil # Falls back to default_image_model
|
|
692
|
+
@default_transformer_model = "sdxl" # SDXL is good for transformations
|
|
693
|
+
@default_upscaler_model = "real-esrgan" # Real-ESRGAN for upscaling
|
|
694
|
+
@default_variation_strength = 0.5 # How different variations should be
|
|
695
|
+
@default_transform_strength = 0.75 # How much to transform
|
|
696
|
+
|
|
697
|
+
# Phase 3: Image Analysis and Background Removal defaults
|
|
698
|
+
@default_analyzer_model = "gpt-4o" # Vision model for analysis
|
|
699
|
+
@default_analysis_type = :detailed # Default analysis type
|
|
700
|
+
@default_analyzer_max_tags = 10 # Max tags to extract
|
|
701
|
+
@default_background_remover_model = "rembg" # Default remover model
|
|
702
|
+
@default_background_output_format = :png # Output format for transparency
|
|
703
|
+
|
|
704
|
+
# Directory structure defaults
|
|
705
|
+
@root_directory = "llm" # Root directory under app/
|
|
706
|
+
@root_namespace = "Llm" # Root namespace for classes
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# Returns the configured cache store, falling back to Rails.cache
|
|
710
|
+
#
|
|
711
|
+
# @return [ActiveSupport::Cache::Store] The cache store instance
|
|
712
|
+
# @example Using a custom cache store
|
|
713
|
+
# config.cache_store = ActiveSupport::Cache::MemoryStore.new
|
|
714
|
+
def cache_store
|
|
715
|
+
@cache_store || Rails.cache
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
# Returns whether budgets are configured and enforcement is enabled
|
|
719
|
+
#
|
|
720
|
+
# @return [Boolean] true if budgets are configured with enforcement
|
|
721
|
+
def budgets_enabled?
|
|
722
|
+
budgets.is_a?(Hash) && budgets[:enforcement] && budgets[:enforcement] != :none
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
# Returns the budget enforcement mode
|
|
726
|
+
#
|
|
727
|
+
# @return [Symbol] :none, :soft, or :hard
|
|
728
|
+
def budget_enforcement
|
|
729
|
+
budgets&.dig(:enforcement) || :none
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# Returns all retryable patterns as a flat array
|
|
733
|
+
#
|
|
734
|
+
# @return [Array<String>] All patterns from all categories
|
|
735
|
+
def all_retryable_patterns
|
|
736
|
+
default_retryable_patterns.values.flatten.uniq
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Returns whether alerts are configured
|
|
740
|
+
#
|
|
741
|
+
# @return [Boolean] true if any alert destination is configured
|
|
742
|
+
def alerts_enabled?
|
|
743
|
+
return false unless alerts.is_a?(Hash)
|
|
744
|
+
|
|
745
|
+
alerts[:slack_webhook_url].present? ||
|
|
746
|
+
alerts[:webhook_url].present? ||
|
|
747
|
+
alerts[:custom].present?
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# Returns the list of events to alert on
|
|
751
|
+
#
|
|
752
|
+
# @return [Array<Symbol>] Event names to trigger alerts
|
|
753
|
+
def alert_events
|
|
754
|
+
alerts&.dig(:on_events) || []
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Returns merged redaction fields (default sensitive keys + configured)
|
|
758
|
+
#
|
|
759
|
+
# @return [Array<String>] Field names to redact
|
|
760
|
+
def redaction_fields
|
|
761
|
+
default_fields = %w[password token api_key secret credential auth key access_token]
|
|
762
|
+
configured_fields = redaction&.dig(:fields) || []
|
|
763
|
+
(default_fields + configured_fields).map(&:downcase).uniq
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Returns redaction patterns
|
|
767
|
+
#
|
|
768
|
+
# @return [Array<Regexp>] Patterns to match and redact
|
|
769
|
+
def redaction_patterns
|
|
770
|
+
redaction&.dig(:patterns) || []
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Returns the redaction placeholder string
|
|
774
|
+
#
|
|
775
|
+
# @return [String] Placeholder to replace redacted values
|
|
776
|
+
def redaction_placeholder
|
|
777
|
+
redaction&.dig(:placeholder) || "[REDACTED]"
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
# Returns the maximum value length before truncation
|
|
781
|
+
#
|
|
782
|
+
# @return [Integer, nil] Max length, or nil for no limit
|
|
783
|
+
def redaction_max_value_length
|
|
784
|
+
redaction&.dig(:max_value_length)
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
# Returns whether multi-tenancy is enabled
|
|
788
|
+
#
|
|
789
|
+
# @return [Boolean] true if multi-tenancy is enabled
|
|
790
|
+
def multi_tenancy_enabled?
|
|
791
|
+
@multi_tenancy_enabled == true
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
# Returns the current tenant ID from the resolver
|
|
795
|
+
#
|
|
796
|
+
# @return [String, nil] Current tenant identifier or nil
|
|
797
|
+
def current_tenant_id
|
|
798
|
+
return nil unless multi_tenancy_enabled?
|
|
799
|
+
|
|
800
|
+
tenant_resolver&.call
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
# Returns whether the async gem is available
|
|
804
|
+
#
|
|
805
|
+
# @return [Boolean] true if async gem is loaded
|
|
806
|
+
def async_available?
|
|
807
|
+
defined?(::Async) && defined?(::Async::Semaphore)
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
# Returns whether we're currently inside an async context
|
|
811
|
+
#
|
|
812
|
+
# @return [Boolean] true if running in a fiber with async scheduler
|
|
813
|
+
def async_context?
|
|
814
|
+
return false unless async_available?
|
|
815
|
+
|
|
816
|
+
defined?(::Async::Task) && ::Async::Task.current?
|
|
817
|
+
rescue StandardError
|
|
818
|
+
false
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# Returns the full namespace for a given category
|
|
822
|
+
#
|
|
823
|
+
# @param category [Symbol, nil] Category (:audio, :image, :text, or nil for root)
|
|
824
|
+
# @return [String, nil] Full namespace string, or nil if no namespace configured
|
|
825
|
+
# @example With default namespace
|
|
826
|
+
# namespace_for(:image) #=> "Llm::Image"
|
|
827
|
+
# namespace_for(nil) #=> "Llm"
|
|
828
|
+
# @example With no namespace (root_namespace = nil)
|
|
829
|
+
# namespace_for(:image) #=> "Image"
|
|
830
|
+
# namespace_for(nil) #=> nil
|
|
831
|
+
def namespace_for(category = nil)
|
|
832
|
+
# Handle no namespace configuration
|
|
833
|
+
if root_namespace.blank?
|
|
834
|
+
case category
|
|
835
|
+
when :audio then "Audio"
|
|
836
|
+
when :image then "Image"
|
|
837
|
+
when :text then "Text"
|
|
838
|
+
else nil
|
|
839
|
+
end
|
|
840
|
+
else
|
|
841
|
+
case category
|
|
842
|
+
when :audio then "#{root_namespace}::Audio"
|
|
843
|
+
when :image then "#{root_namespace}::Image"
|
|
844
|
+
when :text then "#{root_namespace}::Text"
|
|
845
|
+
else root_namespace
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
# Returns the full path under app/ for a given category and type
|
|
851
|
+
#
|
|
852
|
+
# @param category [Symbol, nil] Category (:audio, :image, :text, or nil)
|
|
853
|
+
# @param type [String] Component type (e.g., "agents", "speakers", "generators")
|
|
854
|
+
# @return [String] Full path relative to Rails.root
|
|
855
|
+
# @example
|
|
856
|
+
# path_for(:image, "generators") #=> "app/llm/image/generators"
|
|
857
|
+
# path_for(nil, "agents") #=> "app/llm/agents"
|
|
858
|
+
def path_for(category, type)
|
|
859
|
+
case category
|
|
860
|
+
when :audio then "app/#{root_directory}/audio/#{type}"
|
|
861
|
+
when :image then "app/#{root_directory}/image/#{type}"
|
|
862
|
+
when :text then "app/#{root_directory}/text/#{type}"
|
|
863
|
+
else "app/#{root_directory}/#{type}"
|
|
864
|
+
end
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
# Returns all autoload paths for LLM components
|
|
868
|
+
#
|
|
869
|
+
# @return [Array<String>] List of paths relative to Rails.root
|
|
870
|
+
def all_autoload_paths
|
|
871
|
+
[
|
|
872
|
+
# Top-level
|
|
873
|
+
path_for(nil, "agents"),
|
|
874
|
+
path_for(nil, "workflows"),
|
|
875
|
+
path_for(nil, "tools"),
|
|
876
|
+
# Audio
|
|
877
|
+
path_for(:audio, "speakers"),
|
|
878
|
+
path_for(:audio, "transcribers"),
|
|
879
|
+
# Image
|
|
880
|
+
path_for(:image, "analyzers"),
|
|
881
|
+
path_for(:image, "background_removers"),
|
|
882
|
+
path_for(:image, "editors"),
|
|
883
|
+
path_for(:image, "generators"),
|
|
884
|
+
path_for(:image, "transformers"),
|
|
885
|
+
path_for(:image, "upscalers"),
|
|
886
|
+
path_for(:image, "variators"),
|
|
887
|
+
# Text
|
|
888
|
+
path_for(:text, "embedders"),
|
|
889
|
+
path_for(:text, "moderators")
|
|
890
|
+
]
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
private
|
|
894
|
+
|
|
895
|
+
# Validates that a value is within a range
|
|
896
|
+
#
|
|
897
|
+
# @param attr [Symbol] Attribute name for error message
|
|
898
|
+
# @param value [Numeric] Value to validate
|
|
899
|
+
# @param min [Numeric] Minimum value (inclusive)
|
|
900
|
+
# @param max [Numeric] Maximum value (inclusive)
|
|
901
|
+
# @raise [ArgumentError] If value is outside range
|
|
902
|
+
def validate_range!(attr, value, min, max)
|
|
903
|
+
return if value.is_a?(Numeric) && value >= min && value <= max
|
|
904
|
+
|
|
905
|
+
raise ArgumentError, "#{attr} must be between #{min} and #{max}"
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
# Validates that a value is positive (greater than 0)
|
|
909
|
+
#
|
|
910
|
+
# @param attr [Symbol] Attribute name for error message
|
|
911
|
+
# @param value [Numeric] Value to validate
|
|
912
|
+
# @raise [ArgumentError] If value is not positive
|
|
913
|
+
def validate_positive!(attr, value)
|
|
914
|
+
return if value.is_a?(Numeric) && value > 0
|
|
915
|
+
|
|
916
|
+
raise ArgumentError, "#{attr} must be greater than 0"
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
# Validates that a value is non-negative (>= 0)
|
|
920
|
+
#
|
|
921
|
+
# @param attr [Symbol] Attribute name for error message
|
|
922
|
+
# @param value [Numeric] Value to validate
|
|
923
|
+
# @raise [ArgumentError] If value is negative
|
|
924
|
+
def validate_non_negative!(attr, value)
|
|
925
|
+
return if value.is_a?(Numeric) && value >= 0
|
|
926
|
+
|
|
927
|
+
raise ArgumentError, "#{attr} must be >= 0"
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
# Validates that a value is callable (responds to :call)
|
|
931
|
+
#
|
|
932
|
+
# @param attr [Symbol] Attribute name for error message
|
|
933
|
+
# @param value [Object] Value to validate
|
|
934
|
+
# @param allow_nil [Boolean] Whether nil is allowed
|
|
935
|
+
# @raise [ArgumentError] If value is not callable (or nil when allowed)
|
|
936
|
+
def validate_callable!(attr, value, allow_nil:)
|
|
937
|
+
return if allow_nil && value.nil?
|
|
938
|
+
return if value.respond_to?(:call)
|
|
939
|
+
|
|
940
|
+
if allow_nil
|
|
941
|
+
raise ArgumentError, "#{attr} must be callable or nil"
|
|
942
|
+
else
|
|
943
|
+
raise ArgumentError, "#{attr} must be callable"
|
|
944
|
+
end
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
# Validates retries configuration hash
|
|
948
|
+
#
|
|
949
|
+
# @param value [Hash] Retries configuration
|
|
950
|
+
# @raise [ArgumentError] If any values are invalid
|
|
951
|
+
def validate_retries!(value)
|
|
952
|
+
return unless value.is_a?(Hash)
|
|
953
|
+
|
|
954
|
+
if value.key?(:backoff) && ![:exponential, :constant].include?(value[:backoff])
|
|
955
|
+
raise ArgumentError, "default_retries[:backoff] must be :exponential or :constant"
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
if value.key?(:base) && (!value[:base].is_a?(Numeric) || value[:base] <= 0)
|
|
959
|
+
raise ArgumentError, "default_retries[:base] must be greater than 0"
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
if value.key?(:max_delay) && (!value[:max_delay].is_a?(Numeric) || value[:max_delay] <= 0)
|
|
963
|
+
raise ArgumentError, "default_retries[:max_delay] must be greater than 0"
|
|
964
|
+
end
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
# Validates budgets configuration hash
|
|
968
|
+
#
|
|
969
|
+
# @param value [Hash, nil] Budgets configuration
|
|
970
|
+
# @raise [ArgumentError] If enforcement is invalid
|
|
971
|
+
def validate_budgets!(value)
|
|
972
|
+
return if value.nil?
|
|
973
|
+
return unless value.is_a?(Hash)
|
|
974
|
+
|
|
975
|
+
if value.key?(:enforcement) && ![:none, :soft, :hard].include?(value[:enforcement])
|
|
976
|
+
raise ArgumentError, "budgets[:enforcement] must be :none, :soft, or :hard"
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
end
|