ruby_llm-agents 0.4.0 → 1.0.0.beta.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.
Files changed (208) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +225 -34
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  6. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  8. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  10. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  11. data/app/models/ruby_llm/agents/execution.rb +3 -0
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
  13. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
  15. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  16. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  17. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  18. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  19. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  20. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  21. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  22. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  23. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  24. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  25. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  26. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  27. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  28. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  30. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  31. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  32. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  33. data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
  34. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  35. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  37. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  38. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  39. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  40. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  41. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  42. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  43. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  44. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  45. data/config/routes.rb +13 -1
  46. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  47. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  48. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  49. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  50. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  51. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  52. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  53. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  54. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  55. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  56. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  57. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  58. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  59. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  60. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  61. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  62. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  63. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  64. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  65. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  66. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  67. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  68. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  69. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  70. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  71. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  72. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  73. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  74. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  75. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  76. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  77. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  78. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  79. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  80. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  81. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  82. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  83. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  84. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  85. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  86. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  87. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  88. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  89. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  90. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  91. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  92. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  93. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  94. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  95. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  96. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  97. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  98. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  99. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  100. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  101. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  102. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  103. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  104. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  105. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  106. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  107. data/lib/ruby_llm/agents/core/base.rb +135 -0
  108. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  109. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  110. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
  111. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  112. data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
  113. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  114. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  115. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  116. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  117. data/lib/ruby_llm/agents/dsl.rb +41 -0
  118. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  119. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  120. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  121. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  122. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  123. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  124. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  125. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  126. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  127. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  128. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  129. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  130. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  131. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  132. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  133. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  134. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  135. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  136. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  137. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  138. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  139. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  140. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  141. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  142. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  143. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  144. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  145. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  146. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  147. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  148. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  149. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  150. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  151. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  152. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  153. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  154. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  155. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  156. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  157. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  158. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  159. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  160. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  161. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  162. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  163. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  164. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  165. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  166. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
  167. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  168. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  169. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  170. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  171. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  172. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  173. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  174. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  175. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  176. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  177. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  178. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  179. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  180. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  181. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  182. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  183. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  184. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  185. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  186. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  187. data/lib/ruby_llm/agents.rb +86 -20
  188. metadata +189 -35
  189. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  190. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  191. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  192. data/lib/ruby_llm/agents/base/execution.rb +0 -283
  193. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  194. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  195. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  196. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  197. data/lib/ruby_llm/agents/base.rb +0 -209
  198. data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
  199. data/lib/ruby_llm/agents/configuration.rb +0 -357
  200. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  201. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  202. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  203. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  204. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  205. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  206. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  207. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  208. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module DSL
6
+ # Reliability DSL for retries, fallbacks, and circuit breakers.
7
+ #
8
+ # This module provides configuration methods for reliability features
9
+ # that can be mixed into any agent class.
10
+ #
11
+ # @example Block-style configuration
12
+ # class MyAgent < RubyLLM::Agents::BaseAgent
13
+ # extend DSL::Reliability
14
+ #
15
+ # reliability do
16
+ # retries max: 3, backoff: :exponential
17
+ # fallback_models "gpt-4o-mini"
18
+ # total_timeout 30
19
+ # circuit_breaker errors: 5, within: 60
20
+ # end
21
+ # end
22
+ #
23
+ # @example Individual method configuration
24
+ # class MyAgent < RubyLLM::Agents::BaseAgent
25
+ # extend DSL::Reliability
26
+ #
27
+ # retries max: 3
28
+ # fallback_models "gpt-4o-mini", "gpt-3.5-turbo"
29
+ # end
30
+ #
31
+ module Reliability
32
+ # @!group Reliability DSL
33
+
34
+ # Configures reliability features using a block syntax
35
+ #
36
+ # Groups all reliability configuration in a single block for clarity.
37
+ #
38
+ # @yield Block containing reliability configuration
39
+ # @return [void]
40
+ # @example
41
+ # reliability do
42
+ # retries max: 3, backoff: :exponential
43
+ # fallback_models "gpt-4o-mini"
44
+ # total_timeout 30
45
+ # circuit_breaker errors: 5
46
+ # end
47
+ def reliability(&block)
48
+ builder = ReliabilityBuilder.new
49
+ builder.instance_eval(&block)
50
+
51
+ @retries_config = builder.retries_config if builder.retries_config
52
+ @fallback_models = builder.fallback_models_list if builder.fallback_models_list.any?
53
+ @fallback_providers = builder.fallback_providers_list if builder.fallback_providers_list.any?
54
+ @total_timeout = builder.total_timeout_value if builder.total_timeout_value
55
+ @circuit_breaker_config = builder.circuit_breaker_config if builder.circuit_breaker_config
56
+ @retryable_patterns = builder.retryable_patterns_list if builder.retryable_patterns_list
57
+ end
58
+
59
+ # Returns the complete reliability configuration hash
60
+ #
61
+ # Used by the Reliability middleware to get all settings.
62
+ #
63
+ # @return [Hash, nil] The reliability configuration
64
+ def reliability_config
65
+ return nil unless reliability_configured?
66
+
67
+ {
68
+ retries: retries_config,
69
+ fallback_models: fallback_models,
70
+ fallback_providers: fallback_providers,
71
+ total_timeout: total_timeout,
72
+ circuit_breaker: circuit_breaker_config,
73
+ retryable_patterns: retryable_patterns
74
+ }.compact
75
+ end
76
+
77
+ # Checks if any reliability features are configured
78
+ #
79
+ # @return [Boolean] true if reliability is configured
80
+ def reliability_configured?
81
+ (retries_config && retries_config[:max]&.positive?) ||
82
+ fallback_models.any? ||
83
+ fallback_providers.any? ||
84
+ circuit_breaker_config.present?
85
+ end
86
+
87
+ # Configures retry behavior for this agent
88
+ #
89
+ # @param max [Integer] Maximum number of retry attempts (default: 0)
90
+ # @param backoff [Symbol] Backoff strategy (:constant or :exponential)
91
+ # @param base [Float] Base delay in seconds
92
+ # @param max_delay [Float] Maximum delay between retries
93
+ # @param on [Array<Class>] Error classes to retry on (extends defaults)
94
+ # @return [Hash] The current retry configuration
95
+ # @example
96
+ # retries max: 2, backoff: :exponential
97
+ def retries(max: nil, backoff: nil, base: nil, max_delay: nil, on: nil)
98
+ if max || backoff || base || max_delay || on
99
+ @retries_config ||= default_retries_config.dup
100
+ @retries_config[:max] = max if max
101
+ @retries_config[:backoff] = backoff if backoff
102
+ @retries_config[:base] = base if base
103
+ @retries_config[:max_delay] = max_delay if max_delay
104
+ @retries_config[:on] = on if on
105
+ end
106
+ @retries_config || inherited_retry_config || default_retries_config
107
+ end
108
+
109
+ # Returns the retry configuration
110
+ #
111
+ # @return [Hash, nil] The retry configuration
112
+ def retries_config
113
+ @retries_config || inherited_retry_config
114
+ end
115
+
116
+ # Sets or returns fallback models to try when primary model fails
117
+ #
118
+ # @param models [Array<String>] Model identifiers to use as fallbacks
119
+ # @return [Array<String>] The current fallback models
120
+ # @example
121
+ # fallback_models "gpt-4o-mini", "gpt-3.5-turbo"
122
+ def fallback_models(*models)
123
+ @fallback_models = models.flatten if models.any?
124
+ @fallback_models || inherited_fallback_models || []
125
+ end
126
+
127
+ # Sets or returns fallback providers to try when primary provider fails
128
+ #
129
+ # Used primarily by audio agents (Speaker, Transcriber) that may need
130
+ # to fall back to a different provider entirely.
131
+ #
132
+ # @param provider [Symbol] The provider to fall back to
133
+ # @param options [Hash] Provider-specific options (e.g., voice:, model:)
134
+ # @return [Array<Hash>] The current fallback providers
135
+ # @example
136
+ # fallback_provider :openai, voice: "nova"
137
+ def fallback_provider(provider = nil, **options)
138
+ if provider
139
+ @fallback_providers ||= []
140
+ @fallback_providers << { provider: provider, **options }
141
+ end
142
+ @fallback_providers || inherited_fallback_providers || []
143
+ end
144
+
145
+ # Returns the fallback providers list
146
+ #
147
+ # @return [Array<Hash>] The fallback providers
148
+ def fallback_providers
149
+ @fallback_providers || inherited_fallback_providers || []
150
+ end
151
+
152
+ # Sets or returns the total timeout for all retry/fallback attempts
153
+ #
154
+ # @param seconds [Integer, nil] Total timeout in seconds
155
+ # @return [Integer, nil] The current total timeout
156
+ # @example
157
+ # total_timeout 30
158
+ def total_timeout(seconds = nil)
159
+ @total_timeout = seconds if seconds
160
+ @total_timeout || inherited_total_timeout
161
+ end
162
+
163
+ # Configures circuit breaker for this agent
164
+ #
165
+ # @param errors [Integer] Number of errors to trigger open state
166
+ # @param within [Integer] Rolling window in seconds
167
+ # @param cooldown [Integer] Cooldown period in seconds when open
168
+ # @return [Hash, nil] The current circuit breaker configuration
169
+ # @example
170
+ # circuit_breaker errors: 10, within: 60, cooldown: 300
171
+ def circuit_breaker(errors: nil, within: nil, cooldown: nil)
172
+ if errors || within || cooldown
173
+ @circuit_breaker_config ||= { errors: 10, within: 60, cooldown: 300 }
174
+ @circuit_breaker_config[:errors] = errors if errors
175
+ @circuit_breaker_config[:within] = within if within
176
+ @circuit_breaker_config[:cooldown] = cooldown if cooldown
177
+ end
178
+ @circuit_breaker_config || inherited_circuit_breaker_config
179
+ end
180
+
181
+ # Returns the circuit breaker configuration
182
+ #
183
+ # @return [Hash, nil] The circuit breaker configuration
184
+ def circuit_breaker_config
185
+ @circuit_breaker_config || inherited_circuit_breaker_config
186
+ end
187
+
188
+ # Sets or returns additional retryable patterns for error message matching
189
+ #
190
+ # @param patterns [Array<String>] Pattern strings to match in error messages
191
+ # @return [Array<String>, nil] The current retryable patterns
192
+ # @example
193
+ # retryable_patterns "custom_error", "another_pattern"
194
+ def retryable_patterns(*patterns)
195
+ @retryable_patterns = patterns.flatten if patterns.any?
196
+ @retryable_patterns || inherited_retryable_patterns
197
+ end
198
+
199
+ # @!endgroup
200
+
201
+ private
202
+
203
+ def inherited_retry_config
204
+ return nil unless superclass.respond_to?(:retries_config)
205
+
206
+ superclass.retries_config
207
+ end
208
+
209
+ def inherited_fallback_models
210
+ return nil unless superclass.respond_to?(:fallback_models)
211
+
212
+ superclass.fallback_models
213
+ end
214
+
215
+ def inherited_fallback_providers
216
+ return nil unless superclass.respond_to?(:fallback_providers)
217
+
218
+ superclass.fallback_providers
219
+ end
220
+
221
+ def inherited_total_timeout
222
+ return nil unless superclass.respond_to?(:total_timeout)
223
+
224
+ superclass.total_timeout
225
+ end
226
+
227
+ def inherited_circuit_breaker_config
228
+ return nil unless superclass.respond_to?(:circuit_breaker_config)
229
+
230
+ superclass.circuit_breaker_config
231
+ end
232
+
233
+ def inherited_retryable_patterns
234
+ return nil unless superclass.respond_to?(:retryable_patterns)
235
+
236
+ superclass.retryable_patterns
237
+ end
238
+
239
+ def default_retries_config
240
+ {
241
+ max: 0,
242
+ backoff: :exponential,
243
+ base: 0.4,
244
+ max_delay: 3.0,
245
+ on: []
246
+ }
247
+ end
248
+
249
+ # Inner builder class for block-style configuration
250
+ class ReliabilityBuilder
251
+ attr_reader :retries_config, :fallback_models_list, :total_timeout_value,
252
+ :circuit_breaker_config, :retryable_patterns_list, :fallback_providers_list
253
+
254
+ def initialize
255
+ @retries_config = nil
256
+ @fallback_models_list = []
257
+ @total_timeout_value = nil
258
+ @circuit_breaker_config = nil
259
+ @retryable_patterns_list = nil
260
+ @fallback_providers_list = []
261
+ end
262
+
263
+ def retries(max: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [])
264
+ @retries_config = {
265
+ max: max,
266
+ backoff: backoff,
267
+ base: base,
268
+ max_delay: max_delay,
269
+ on: on
270
+ }
271
+ end
272
+
273
+ def fallback_models(*models)
274
+ @fallback_models_list = models.flatten
275
+ end
276
+
277
+ # Configures a fallback provider with optional settings
278
+ #
279
+ # @param provider [Symbol] The provider to fall back to (e.g., :openai, :elevenlabs)
280
+ # @param options [Hash] Provider-specific options (e.g., voice:, model:)
281
+ # @example
282
+ # fallback_provider :openai, voice: "nova"
283
+ # fallback_provider :elevenlabs, voice: "Rachel", model: "eleven_multilingual_v2"
284
+ def fallback_provider(provider, **options)
285
+ @fallback_providers_list << { provider: provider, **options }
286
+ end
287
+
288
+ def total_timeout(seconds)
289
+ @total_timeout_value = seconds
290
+ end
291
+
292
+ def circuit_breaker(errors: 10, within: 60, cooldown: 300)
293
+ @circuit_breaker_config = {
294
+ errors: errors,
295
+ within: within,
296
+ cooldown: cooldown
297
+ }
298
+ end
299
+
300
+ def retryable_patterns(*patterns)
301
+ @retryable_patterns_list = patterns.flatten
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dsl/base"
4
+ require_relative "dsl/reliability"
5
+ require_relative "dsl/caching"
6
+
7
+ module RubyLLM
8
+ module Agents
9
+ # Domain-Specific Language modules for agent configuration.
10
+ #
11
+ # The DSL modules provide a clean, declarative way to configure agents
12
+ # at the class level. Each module focuses on a specific concern:
13
+ #
14
+ # - {DSL::Base} - Core settings (model, version, description, timeout)
15
+ # - {DSL::Reliability} - Retries, fallbacks, circuit breakers
16
+ # - {DSL::Caching} - Response caching configuration
17
+ #
18
+ # @example Using all DSL modules
19
+ # class MyAgent < RubyLLM::Agents::BaseAgent
20
+ # extend DSL::Base
21
+ # extend DSL::Reliability
22
+ # extend DSL::Caching
23
+ #
24
+ # model "gpt-4o"
25
+ # version "2.0"
26
+ # description "A helpful agent"
27
+ # timeout 30
28
+ #
29
+ # reliability do
30
+ # retries max: 3, backoff: :exponential
31
+ # fallback_models "gpt-4o-mini"
32
+ # circuit_breaker errors: 5, within: 60
33
+ # end
34
+ #
35
+ # cache_for 1.hour
36
+ # end
37
+ #
38
+ module DSL
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../concerns/image_operation_dsl"
4
+
5
+ module RubyLLM
6
+ module Agents
7
+ class ImageAnalyzer
8
+ # DSL for configuring image analyzers
9
+ #
10
+ # Provides class-level methods to configure model, analysis type,
11
+ # and feature extraction options.
12
+ #
13
+ # @example
14
+ # class ProductAnalyzer < RubyLLM::Agents::ImageAnalyzer
15
+ # model "gpt-4o"
16
+ # analysis_type :detailed
17
+ # extract_colors true
18
+ # detect_objects true
19
+ # max_tags 20
20
+ # end
21
+ #
22
+ module DSL
23
+ include Concerns::ImageOperationDSL
24
+
25
+ VALID_ANALYSIS_TYPES = %i[caption detailed tags objects colors all].freeze
26
+
27
+ # Set or get the analysis type
28
+ #
29
+ # @param value [Symbol, nil] Analysis type (:caption, :detailed, :tags, :objects, :colors, :all)
30
+ # @return [Symbol] The analysis type
31
+ def analysis_type(value = nil)
32
+ if value
33
+ unless VALID_ANALYSIS_TYPES.include?(value)
34
+ raise ArgumentError, "Analysis type must be one of: #{VALID_ANALYSIS_TYPES.join(', ')}"
35
+ end
36
+ @analysis_type = value
37
+ else
38
+ @analysis_type || inherited_or_default(:analysis_type, :detailed)
39
+ end
40
+ end
41
+
42
+ # Set or get whether to extract colors
43
+ #
44
+ # When enabled, extracts dominant colors from the image with
45
+ # hex values, names, and percentages.
46
+ #
47
+ # @param value [Boolean, nil] Enable color extraction
48
+ # @return [Boolean] Whether color extraction is enabled
49
+ def extract_colors(value = nil)
50
+ if value.nil?
51
+ result = @extract_colors
52
+ result = inherited_or_default(:extract_colors, false) if result.nil?
53
+ result
54
+ else
55
+ @extract_colors = value
56
+ end
57
+ end
58
+
59
+ # Set or get whether to detect objects
60
+ #
61
+ # When enabled, detects and identifies objects in the image
62
+ # with bounding boxes and confidence scores.
63
+ #
64
+ # @param value [Boolean, nil] Enable object detection
65
+ # @return [Boolean] Whether object detection is enabled
66
+ def detect_objects(value = nil)
67
+ if value.nil?
68
+ result = @detect_objects
69
+ result = inherited_or_default(:detect_objects, false) if result.nil?
70
+ result
71
+ else
72
+ @detect_objects = value
73
+ end
74
+ end
75
+
76
+ # Set or get whether to extract text (OCR)
77
+ #
78
+ # When enabled, extracts text visible in the image using OCR.
79
+ #
80
+ # @param value [Boolean, nil] Enable text extraction
81
+ # @return [Boolean] Whether text extraction is enabled
82
+ def extract_text(value = nil)
83
+ if value.nil?
84
+ result = @extract_text
85
+ result = inherited_or_default(:extract_text, false) if result.nil?
86
+ result
87
+ else
88
+ @extract_text = value
89
+ end
90
+ end
91
+
92
+ # Set or get a custom analysis prompt
93
+ #
94
+ # Allows specifying a custom prompt for the vision model
95
+ # to customize what information is extracted.
96
+ #
97
+ # @param value [String, nil] Custom prompt
98
+ # @return [String, nil] The custom prompt
99
+ def custom_prompt(value = nil)
100
+ if value
101
+ @custom_prompt = value
102
+ else
103
+ @custom_prompt || inherited_or_default(:custom_prompt, nil)
104
+ end
105
+ end
106
+
107
+ # Set or get maximum number of tags
108
+ #
109
+ # @param value [Integer, nil] Maximum tags to return
110
+ # @return [Integer] The maximum number of tags
111
+ def max_tags(value = nil)
112
+ if value
113
+ unless value.is_a?(Integer) && value > 0
114
+ raise ArgumentError, "Max tags must be a positive integer"
115
+ end
116
+ @max_tags = value
117
+ else
118
+ @max_tags || inherited_or_default(:max_tags, 10)
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def default_model
125
+ config.default_analyzer_model || "gpt-4o"
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end