ruby_llm-agents 1.3.3 → 2.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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -334
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +46 -10
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +52 -6
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +58 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
  99. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  100. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  101. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  102. data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
  103. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  104. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  105. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  106. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  107. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  108. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  109. data/lib/ruby_llm/agents/results/base.rb +1 -49
  110. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  111. data/lib/ruby_llm/agents.rb +1 -9
  112. data/lib/tasks/ruby_llm_agents.rake +34 -0
  113. metadata +12 -81
  114. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  115. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  116. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  117. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  118. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  119. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  120. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  121. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  122. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  123. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  125. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  126. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  127. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  128. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  129. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  130. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  131. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  132. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  133. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  134. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  135. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  136. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  137. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  138. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  139. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  140. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  141. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  142. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  143. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  144. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  145. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  146. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  147. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  148. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  149. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  150. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  151. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  152. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  153. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  154. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  155. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  156. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  157. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  158. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  159. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  160. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  161. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  162. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  163. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  164. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  165. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  166. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  167. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  168. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  169. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  170. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  171. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  172. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  173. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  174. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  175. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  176. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  177. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  178. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  180. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  181. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  182. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  183. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  185. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  186. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  187. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  188. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  189. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  190. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  191. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  192. data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
@@ -7,20 +7,39 @@ module RubyLLM
7
7
  #
8
8
  # Provides common configuration methods that every agent type needs:
9
9
  # - model: The LLM model to use
10
- # - version: Cache invalidation version
10
+ # - prompt: The user prompt (string with {placeholders} or block)
11
+ # - system: System instructions
11
12
  # - description: Human-readable description
12
13
  # - timeout: Request timeout
14
+ # - returns: Structured output schema
13
15
  #
14
- # @example Basic usage
15
- # class MyAgent < RubyLLM::Agents::BaseAgent
16
- # extend DSL::Base
17
- #
16
+ # @example Simplified DSL
17
+ # class SearchAgent < RubyLLM::Agents::BaseAgent
18
18
  # model "gpt-4o"
19
- # version "2.0"
20
- # description "A helpful agent"
19
+ # system "You are a helpful search assistant."
20
+ # prompt "Search for: {query} (limit: {limit})"
21
+ #
22
+ # param :limit, default: 10 # Override auto-detected param
23
+ #
24
+ # returns do
25
+ # array :results do
26
+ # string :title
27
+ # string :url
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @example Dynamic prompt with block
33
+ # class SummaryAgent < RubyLLM::Agents::BaseAgent
34
+ # prompt do
35
+ # "Summarize in #{word_count} words: #{text}"
36
+ # end
21
37
  # end
22
38
  #
23
39
  module Base
40
+ # Regex pattern to extract {placeholder} parameters from prompt strings
41
+ PLACEHOLDER_PATTERN = /\{(\w+)\}/.freeze
42
+
24
43
  # @!group Configuration DSL
25
44
 
26
45
  # Sets or returns the LLM model for this agent class
@@ -34,18 +53,75 @@ module RubyLLM
34
53
  @model || inherited_or_default(:model, default_model)
35
54
  end
36
55
 
37
- # Sets or returns the version string for cache invalidation
56
+ # Sets the user prompt template or block
38
57
  #
39
- # Change this when you want to invalidate cached results
40
- # (e.g., after changing prompts or behavior).
58
+ # When a string is provided, {placeholder} syntax is used to interpolate
59
+ # parameters. Parameters are automatically registered (as required) unless
60
+ # already defined with `param`.
41
61
  #
42
- # @param value [String, nil] Version string
43
- # @return [String] The current version
44
- # @example
45
- # version "2.0"
46
- def version(value = nil)
47
- @version = value if value
48
- @version || inherited_or_default(:version, "1.0")
62
+ # When a block is provided, it's evaluated in the instance context at
63
+ # execution time, allowing access to all instance methods and parameters.
64
+ #
65
+ # @param template [String, nil] Prompt template with {placeholder} syntax
66
+ # @yield Block that returns the prompt string (evaluated at execution time)
67
+ # @return [String, Proc, nil] The current prompt configuration
68
+ #
69
+ # @example With template string (parameters auto-detected)
70
+ # prompt "Search for: {query} in {category}"
71
+ # # Automatically registers :query and :category as required params
72
+ #
73
+ # @example With block for dynamic prompts
74
+ # prompt do
75
+ # base = "Analyze the following"
76
+ # base += " in #{language}" if language != "en"
77
+ # "#{base}: #{text}"
78
+ # end
79
+ #
80
+ def prompt(template = nil, &block)
81
+ if template
82
+ @prompt_template = template
83
+ auto_register_params_from_template(template)
84
+ elsif block
85
+ @prompt_block = block
86
+ end
87
+ @prompt_template || @prompt_block || inherited_or_default(:prompt_config, nil)
88
+ end
89
+
90
+ # Returns the prompt configuration (template or block)
91
+ #
92
+ # @return [String, Proc, nil] The prompt template, block, or nil
93
+ def prompt_config
94
+ @prompt_template || @prompt_block || inherited_or_default(:prompt_config, nil)
95
+ end
96
+
97
+ # Sets the system prompt/instructions
98
+ #
99
+ # @param text [String, nil] System instructions for the LLM
100
+ # @yield Block that returns the system prompt (evaluated at execution time)
101
+ # @return [String, Proc, nil] The current system prompt
102
+ #
103
+ # @example Static system prompt
104
+ # system "You are a helpful assistant. Be concise and accurate."
105
+ #
106
+ # @example Dynamic system prompt
107
+ # system do
108
+ # "You are helping #{user_name}. Their preferences: #{preferences}"
109
+ # end
110
+ #
111
+ def system(text = nil, &block)
112
+ if text
113
+ @system_template = text
114
+ elsif block
115
+ @system_block = block
116
+ end
117
+ @system_template || @system_block || inherited_or_default(:system_config, nil)
118
+ end
119
+
120
+ # Returns the system prompt configuration
121
+ #
122
+ # @return [String, Proc, nil] The system template, block, or nil
123
+ def system_config
124
+ @system_template || @system_block || inherited_or_default(:system_config, nil)
49
125
  end
50
126
 
51
127
  # Sets or returns the description for this agent class
@@ -72,10 +148,74 @@ module RubyLLM
72
148
  @timeout || inherited_or_default(:timeout, default_timeout)
73
149
  end
74
150
 
151
+ # Sets or returns the response schema for structured output
152
+ #
153
+ # Accepts a hash (JSON Schema), a block (passed to RubyLLM::Schema.create),
154
+ # or any object that responds to `to_json_schema`.
155
+ #
156
+ # @param value [Hash, Object, nil] The schema to set
157
+ # @param block [Proc, nil] Block passed to RubyLLM::Schema.create
158
+ # @return [Hash, RubyLLM::Schema, nil] The current schema setting
159
+ # @example With a block (recommended)
160
+ # schema do
161
+ # string :name, description: "The user's name"
162
+ # integer :age, description: "The user's age"
163
+ # end
164
+ # @example With a hash
165
+ # schema type: "object", properties: { name: { type: "string" } }
166
+ def schema(value = nil, &block)
167
+ if value
168
+ @schema = value
169
+ elsif block
170
+ @schema = RubyLLM::Schema.create(&block)
171
+ end
172
+ @schema || inherited_or_default(:schema, nil)
173
+ end
174
+
175
+ # Alias for schema with a clearer name
176
+ #
177
+ # Defines the structured output schema for this agent.
178
+ # This is the preferred method for defining schemas in the simplified DSL.
179
+ #
180
+ # @param block [Proc] Block passed to RubyLLM::Schema.create
181
+ # @return [RubyLLM::Schema, nil] The current schema setting
182
+ #
183
+ # @example
184
+ # returns do
185
+ # string :summary, "A brief summary"
186
+ # array :insights, of: :string, description: "Key insights"
187
+ # number :confidence, "Confidence score from 0 to 1"
188
+ # end
189
+ #
190
+ def returns(&block)
191
+ schema(&block)
192
+ end
193
+
75
194
  # @!endgroup
76
195
 
77
196
  private
78
197
 
198
+ # Auto-registers parameters found in prompt template placeholders
199
+ #
200
+ # Extracts {placeholder} patterns from the template and registers
201
+ # each as a required parameter (unless already defined).
202
+ #
203
+ # @param template [String] The prompt template
204
+ # @return [void]
205
+ def auto_register_params_from_template(template)
206
+ return unless respond_to?(:param)
207
+
208
+ placeholders = template.scan(PLACEHOLDER_PATTERN).flatten.map(&:to_sym)
209
+ existing_params = respond_to?(:params) ? params.keys : []
210
+
211
+ placeholders.each do |placeholder|
212
+ next if existing_params.include?(placeholder)
213
+
214
+ # Auto-register as required parameter
215
+ param(placeholder, required: true)
216
+ end
217
+ end
218
+
79
219
  # Looks up setting from superclass or uses default
80
220
  #
81
221
  # @param method [Symbol] The method to call on superclass
@@ -43,8 +43,39 @@ module RubyLLM
43
43
  @cache_ttl = ttl
44
44
  end
45
45
 
46
- # Alias for cache_for (for backward compatibility)
47
- alias cache cache_for
46
+ # Unified cache configuration method (simplified DSL)
47
+ #
48
+ # Configures caching with a cleaner syntax using keyword arguments.
49
+ #
50
+ # @param ttl_or_options [ActiveSupport::Duration, Hash] TTL or options hash
51
+ # @param for_duration [ActiveSupport::Duration] TTL for cached responses
52
+ # @param key [Array<Symbol>] Parameters to include in cache key
53
+ # @return [void]
54
+ #
55
+ # @example Simple TTL (positional argument for backward compatibility)
56
+ # cache 1.hour
57
+ #
58
+ # @example With keyword arguments (preferred)
59
+ # cache for: 1.hour
60
+ # cache for: 30.minutes, key: [:query, :user_id]
61
+ #
62
+ def cache(ttl_or_options = nil, for: nil, key: nil)
63
+ # Handle positional argument (backward compatibility)
64
+ if ttl_or_options && !ttl_or_options.is_a?(Hash)
65
+ @cache_enabled = true
66
+ @cache_ttl = ttl_or_options
67
+ return
68
+ end
69
+
70
+ # Handle keyword arguments
71
+ for_duration = binding.local_variable_get(:for)
72
+ if for_duration
73
+ @cache_enabled = true
74
+ @cache_ttl = for_duration
75
+ end
76
+
77
+ @cache_key_includes = Array(key) if key
78
+ end
48
79
 
49
80
  # Returns whether caching is enabled for this agent
50
81
  #
@@ -57,6 +57,35 @@ module RubyLLM
57
57
  @non_fallback_errors = builder.non_fallback_errors_list if builder.non_fallback_errors_list
58
58
  end
59
59
 
60
+ # Alias for reliability with clearer intent-revealing name
61
+ #
62
+ # Configures what happens when an LLM call fails.
63
+ # This is the preferred method in the simplified DSL.
64
+ #
65
+ # @yield Block containing failure handling configuration
66
+ # @return [void]
67
+ #
68
+ # @example
69
+ # on_failure do
70
+ # retry times: 3, backoff: :exponential
71
+ # fallback to: ["gpt-4o-mini", "gpt-3.5-turbo"]
72
+ # circuit_breaker after: 5, cooldown: 5.minutes
73
+ # timeout 30.seconds
74
+ # end
75
+ #
76
+ def on_failure(&block)
77
+ builder = OnFailureBuilder.new
78
+ builder.instance_eval(&block)
79
+
80
+ @retries_config = builder.retries_config if builder.retries_config
81
+ @fallback_models = builder.fallback_models_list if builder.fallback_models_list.any?
82
+ @fallback_providers = builder.fallback_providers_list if builder.fallback_providers_list.any?
83
+ @total_timeout = builder.total_timeout_value if builder.total_timeout_value
84
+ @circuit_breaker_config = builder.circuit_breaker_config if builder.circuit_breaker_config
85
+ @retryable_patterns = builder.retryable_patterns_list if builder.retryable_patterns_list
86
+ @non_fallback_errors = builder.non_fallback_errors_list if builder.non_fallback_errors_list
87
+ end
88
+
60
89
  # Returns the complete reliability configuration hash
61
90
  #
62
91
  # Used by the Reliability middleware to get all settings.
@@ -326,6 +355,125 @@ module RubyLLM
326
355
  @non_fallback_errors_list = error_classes.flatten
327
356
  end
328
357
  end
358
+
359
+ # Builder class for on_failure block with simplified syntax
360
+ #
361
+ # Uses more intuitive method names:
362
+ # - `retry times:` instead of `retries max:`
363
+ # - `fallback to:` instead of `fallback_models`
364
+ # - `circuit_breaker after:` instead of `circuit_breaker errors:`
365
+ # - `timeout` instead of `total_timeout`
366
+ #
367
+ class OnFailureBuilder
368
+ attr_reader :retries_config, :fallback_models_list, :total_timeout_value,
369
+ :circuit_breaker_config, :retryable_patterns_list, :fallback_providers_list,
370
+ :non_fallback_errors_list
371
+
372
+ def initialize
373
+ @retries_config = nil
374
+ @fallback_models_list = []
375
+ @total_timeout_value = nil
376
+ @circuit_breaker_config = nil
377
+ @retryable_patterns_list = nil
378
+ @fallback_providers_list = []
379
+ @non_fallback_errors_list = nil
380
+ end
381
+
382
+ # Configure retry behavior
383
+ #
384
+ # @param times [Integer] Number of retry attempts
385
+ # @param backoff [Symbol] Backoff strategy (:constant or :exponential)
386
+ # @param base [Float] Base delay in seconds
387
+ # @param max_delay [Float] Maximum delay between retries
388
+ # @param on [Array<Class>] Error classes to retry on
389
+ #
390
+ # @example
391
+ # retries times: 3, backoff: :exponential
392
+ #
393
+ def retries(times: 0, backoff: :exponential, base: 0.4, max_delay: 3.0, on: [])
394
+ @retries_config = {
395
+ max: times,
396
+ backoff: backoff,
397
+ base: base,
398
+ max_delay: max_delay,
399
+ on: on
400
+ }
401
+ end
402
+
403
+ # Configure fallback models
404
+ #
405
+ # @param to [String, Array<String>] Model(s) to fall back to
406
+ #
407
+ # @example
408
+ # fallback to: "gpt-4o-mini"
409
+ # fallback to: ["gpt-4o-mini", "gpt-3.5-turbo"]
410
+ #
411
+ def fallback(to:)
412
+ @fallback_models_list = Array(to)
413
+ end
414
+
415
+ # Also support fallback_models for compatibility
416
+ def fallback_models(*models)
417
+ @fallback_models_list = models.flatten
418
+ end
419
+
420
+ # Configure a fallback provider (for audio agents)
421
+ #
422
+ # @param provider [Symbol] The provider to fall back to
423
+ # @param options [Hash] Provider-specific options
424
+ #
425
+ def fallback_provider(provider, **options)
426
+ @fallback_providers_list << { provider: provider, **options }
427
+ end
428
+
429
+ # Configure timeout for all retry/fallback attempts
430
+ #
431
+ # @param duration [Integer, ActiveSupport::Duration] Timeout duration
432
+ #
433
+ # @example
434
+ # timeout 30
435
+ # timeout 30.seconds
436
+ #
437
+ def timeout(duration)
438
+ # Handle ActiveSupport::Duration
439
+ @total_timeout_value = duration.respond_to?(:to_i) ? duration.to_i : duration
440
+ end
441
+
442
+ # Also support total_timeout for compatibility
443
+ alias total_timeout timeout
444
+
445
+ # Configure circuit breaker
446
+ #
447
+ # @param after [Integer] Number of errors to trigger open state
448
+ # @param errors [Integer] Alias for after (compatibility)
449
+ # @param within [Integer] Rolling window in seconds
450
+ # @param cooldown [Integer, ActiveSupport::Duration] Cooldown period
451
+ #
452
+ # @example
453
+ # circuit_breaker after: 5, cooldown: 5.minutes
454
+ # circuit_breaker errors: 10, within: 60, cooldown: 300
455
+ #
456
+ def circuit_breaker(after: nil, errors: nil, within: 60, cooldown: 300)
457
+ error_threshold = after || errors || 10
458
+ cooldown_seconds = cooldown.respond_to?(:to_i) ? cooldown.to_i : cooldown
459
+
460
+ @circuit_breaker_config = {
461
+ errors: error_threshold,
462
+ within: within,
463
+ cooldown: cooldown_seconds
464
+ }
465
+ end
466
+
467
+ # Configure additional retryable patterns
468
+ def retryable_patterns(*patterns)
469
+ @retryable_patterns_list = patterns.flatten
470
+ end
471
+
472
+ # Configure errors that should never trigger fallback
473
+ def non_fallback_errors(*error_classes)
474
+ @non_fallback_errors_list = error_classes.flatten
475
+ end
476
+ end
329
477
  end
330
478
  end
331
479
  end
@@ -11,7 +11,7 @@ module RubyLLM
11
11
  # The DSL modules provide a clean, declarative way to configure agents
12
12
  # at the class level. Each module focuses on a specific concern:
13
13
  #
14
- # - {DSL::Base} - Core settings (model, version, description, timeout)
14
+ # - {DSL::Base} - Core settings (model, description, timeout)
15
15
  # - {DSL::Reliability} - Retries, fallbacks, circuit breakers
16
16
  # - {DSL::Caching} - Response caching configuration
17
17
  #
@@ -22,7 +22,6 @@ module RubyLLM
22
22
  # extend DSL::Caching
23
23
  #
24
24
  # model "gpt-4o"
25
- # version "2.0"
26
25
  # description "A helpful agent"
27
26
  # timeout 30
28
27
  #
@@ -365,7 +365,6 @@ module RubyLLM
365
365
  [
366
366
  "image_analyzer",
367
367
  self.class.name,
368
- self.class.version,
369
368
  resolve_model,
370
369
  resolve_analysis_type.to_s,
371
370
  resolve_extract_colors.to_s,
@@ -387,7 +386,7 @@ module RubyLLM
387
386
  end
388
387
  end
389
388
 
390
- def build_execution_metadata(result)
389
+ def build_metadata(result)
391
390
  {
392
391
  analysis_type: result.analysis_type,
393
392
  tags_count: result.tags.size,
@@ -203,7 +203,6 @@ module RubyLLM
203
203
  [
204
204
  "background_remover",
205
205
  self.class.name,
206
- self.class.version,
207
206
  resolve_model,
208
207
  resolve_output_format.to_s,
209
208
  resolve_alpha_matting.to_s,
@@ -226,7 +225,7 @@ module RubyLLM
226
225
  end
227
226
  end
228
227
 
229
- def build_execution_metadata(result)
228
+ def build_metadata(result)
230
229
  {
231
230
  output_format: result.output_format,
232
231
  alpha_matting: result.alpha_matting,
@@ -5,7 +5,7 @@ module RubyLLM
5
5
  module Concerns
6
6
  # Shared DSL methods for all image operation classes
7
7
  #
8
- # Provides common configuration options like model, version,
8
+ # Provides common configuration options like model,
9
9
  # description, and caching that are shared across ImageVariator,
10
10
  # ImageEditor, ImageTransformer, and ImageUpscaler.
11
11
  #
@@ -22,18 +22,6 @@ module RubyLLM
22
22
  end
23
23
  end
24
24
 
25
- # Set or get the version
26
- #
27
- # @param value [String, nil] Version identifier
28
- # @return [String] The version
29
- def version(value = nil)
30
- if value
31
- @version = value
32
- else
33
- @version || inherited_or_default(:version, "v1")
34
- end
35
- end
36
-
37
25
  # Set or get the description
38
26
  #
39
27
  # @param value [String, nil] Description
@@ -118,7 +118,7 @@ module RubyLLM
118
118
  duration_ms: result.duration_ms,
119
119
  started_at: result.started_at,
120
120
  completed_at: result.completed_at,
121
- metadata: build_execution_metadata(result)
121
+ metadata: build_metadata(result)
122
122
  }
123
123
  end
124
124
 
@@ -141,7 +141,7 @@ module RubyLLM
141
141
  }
142
142
  end
143
143
 
144
- def build_execution_metadata(result)
144
+ def build_metadata(result)
145
145
  { count: result.count }
146
146
  end
147
147
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../concerns/image_operation_dsl"
4
- require_relative "../generator/content_policy"
5
4
 
6
5
  module RubyLLM
7
6
  module Agents
@@ -15,7 +14,6 @@ module RubyLLM
15
14
  # class ProductEditor < RubyLLM::Agents::ImageEditor
16
15
  # model "gpt-image-1"
17
16
  # size "1024x1024"
18
- # content_policy :strict
19
17
  # end
20
18
  #
21
19
  module DSL
@@ -33,18 +31,6 @@ module RubyLLM
33
31
  end
34
32
  end
35
33
 
36
- # Set or get the content policy level
37
- #
38
- # @param level [Symbol, nil] Policy level (:none, :standard, :moderate, :strict)
39
- # @return [Symbol] The content policy level
40
- def content_policy(level = nil)
41
- if level
42
- @content_policy = level
43
- else
44
- @content_policy || inherited_or_default(:content_policy, :standard)
45
- end
46
- end
47
-
48
34
  private
49
35
 
50
36
  def default_model
@@ -23,7 +23,6 @@ module RubyLLM
23
23
  resolve_tenant_context!
24
24
  check_budget! if budget_tracking_enabled?
25
25
  validate_inputs!
26
- validate_content_policy!
27
26
 
28
27
  # Check cache
29
28
  cached = check_cache(ImageEditResult) if cache_enabled?
@@ -81,13 +80,6 @@ module RubyLLM
81
80
  end
82
81
  end
83
82
 
84
- def validate_content_policy!
85
- policy = self.class.content_policy
86
- return if policy == :none || policy == :standard
87
-
88
- ImageGenerator::ContentPolicy.validate!(prompt, policy)
89
- end
90
-
91
83
  def edit_images
92
84
  count = resolve_count
93
85
 
@@ -173,7 +165,6 @@ module RubyLLM
173
165
  [
174
166
  "image_editor",
175
167
  self.class.name,
176
- self.class.version,
177
168
  resolve_model,
178
169
  resolve_size,
179
170
  Digest::SHA256.hexdigest(prompt),
@@ -194,7 +185,7 @@ module RubyLLM
194
185
  end
195
186
  end
196
187
 
197
- def build_execution_metadata(result)
188
+ def build_metadata(result)
198
189
  {
199
190
  count: result.count,
200
191
  size: result.size,
@@ -57,7 +57,6 @@ module RubyLLM
57
57
  subclass.instance_variable_set(:@version, @version)
58
58
  subclass.instance_variable_set(:@description, @description)
59
59
  subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
60
- subclass.instance_variable_set(:@content_policy, @content_policy)
61
60
  end
62
61
  end
63
62
 
@@ -80,15 +80,6 @@ module RubyLLM
80
80
  @style || inherited_or_default(:style, default_image_style)
81
81
  end
82
82
 
83
- # Sets or returns the content policy level
84
- #
85
- # @param level [Symbol, nil] Policy level (:none, :standard, :moderate, :strict)
86
- # @return [Symbol] The content policy level
87
- def content_policy(level = nil)
88
- @content_policy = level if level
89
- @content_policy || inherited_or_default(:content_policy, :standard)
90
- end
91
-
92
83
  # Sets or returns negative prompt (things to avoid in generation)
93
84
  #
94
85
  # @param value [String, nil] Negative prompt text
@@ -162,7 +153,6 @@ module RubyLLM
162
153
  subclass.instance_variable_set(:@version, @version)
163
154
  subclass.instance_variable_set(:@description, @description)
164
155
  subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
165
- subclass.instance_variable_set(:@content_policy, @content_policy)
166
156
  subclass.instance_variable_set(:@negative_prompt, @negative_prompt)
167
157
  subclass.instance_variable_set(:@seed, @seed)
168
158
  subclass.instance_variable_set(:@guidance_scale, @guidance_scale)
@@ -246,7 +236,6 @@ module RubyLLM
246
236
  execution_started_at = Time.current
247
237
 
248
238
  validate_prompt!
249
- validate_content_policy!
250
239
 
251
240
  # Generate image(s)
252
241
  images = generate_images
@@ -286,7 +275,6 @@ module RubyLLM
286
275
  "ruby_llm_agents",
287
276
  "image_generator",
288
277
  self.class.name,
289
- self.class.version,
290
278
  resolved_model,
291
279
  resolved_size,
292
280
  resolved_quality,
@@ -323,14 +311,6 @@ module RubyLLM
323
311
  end
324
312
  end
325
313
 
326
- # Validates prompt against content policy
327
- def validate_content_policy!
328
- policy = self.class.content_policy
329
- return if policy == :none || policy == :standard
330
-
331
- ContentPolicy.validate!(prompt, policy)
332
- end
333
-
334
314
  # Generate images using RubyLLM.paint
335
315
  def generate_images
336
316
  count = @runtime_count
@@ -450,6 +430,5 @@ end
450
430
 
451
431
  # Load supporting modules after class is defined (they reopen the class)
452
432
  require_relative "generator/pricing"
453
- require_relative "generator/content_policy"
454
433
  require_relative "generator/templates"
455
434
  require_relative "generator/active_storage_support"
@@ -14,7 +14,6 @@ module RubyLLM
14
14
  # step :upscale, upscaler: PhotoUpscaler, scale: 4
15
15
  # step :analyze, analyzer: ProductAnalyzer
16
16
  #
17
- # version "1.0"
18
17
  # description "Complete product image pipeline"
19
18
  # stop_on_error true
20
19
  # end
@@ -103,18 +102,6 @@ module RubyLLM
103
102
  @callbacks ||= { before: [], after: [] }
104
103
  end
105
104
 
106
- # Set or get the version
107
- #
108
- # @param value [String, nil] Version identifier
109
- # @return [String] The version
110
- def version(value = nil)
111
- if value
112
- @version = value
113
- else
114
- @version || inherited_or_default(:version, "v1")
115
- end
116
- end
117
-
118
105
  # Set or get the description
119
106
  #
120
107
  # @param value [String, nil] Description