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,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require_relative "../concerns/image_operation_execution"
5
+
6
+ module RubyLLM
7
+ module Agents
8
+ class BackgroundRemover
9
+ # Execution logic for background removers
10
+ #
11
+ # Handles image validation, budget tracking, caching,
12
+ # background removal execution, and result building.
13
+ #
14
+ module Execution
15
+ include Concerns::ImageOperationExecution
16
+
17
+ # Execute the background removal pipeline
18
+ #
19
+ # @return [BackgroundRemovalResult] The result containing extracted subject
20
+ def execute
21
+ started_at = Time.current
22
+
23
+ resolve_tenant_context!
24
+ check_budget! if budget_tracking_enabled?
25
+ validate_image!
26
+
27
+ # Check cache
28
+ cached = check_cache(BackgroundRemovalResult) if cache_enabled?
29
+ return cached if cached
30
+
31
+ # Remove background
32
+ removal_result = remove_background
33
+
34
+ # Build result
35
+ result = build_result(
36
+ foreground: removal_result[:foreground],
37
+ mask: removal_result[:mask],
38
+ started_at: started_at,
39
+ completed_at: Time.current
40
+ )
41
+
42
+ # Cache result
43
+ write_cache(result) if cache_enabled?
44
+
45
+ # Track execution
46
+ record_execution(result) if execution_tracking_enabled?
47
+
48
+ result
49
+ rescue StandardError => e
50
+ record_failed_execution(e, started_at) if execution_tracking_enabled?
51
+ build_error_result(e, started_at)
52
+ end
53
+
54
+ private
55
+
56
+ def execution_type
57
+ "background_removal"
58
+ end
59
+
60
+ def validate_image!
61
+ raise ArgumentError, "Image cannot be blank" if image.nil?
62
+
63
+ # Validate image exists if it's a path
64
+ if image.is_a?(String) && !image.start_with?("http")
65
+ unless File.exist?(image)
66
+ raise ArgumentError, "Image file does not exist: #{image}"
67
+ end
68
+ end
69
+ end
70
+
71
+ def remove_background
72
+ # Try different approaches based on available capabilities
73
+ if RubyLLM.respond_to?(:remove_background)
74
+ remove_via_ruby_llm
75
+ else
76
+ remove_via_provider
77
+ end
78
+ end
79
+
80
+ def remove_via_ruby_llm
81
+ result = RubyLLM.remove_background(
82
+ image: image,
83
+ model: resolve_model,
84
+ **build_removal_options
85
+ )
86
+
87
+ {
88
+ foreground: result,
89
+ mask: result.respond_to?(:mask) ? result.mask : nil
90
+ }
91
+ end
92
+
93
+ def remove_via_provider
94
+ # Use the model through RubyLLM's paint with segmentation/removal mode
95
+ # This handles models like segment-anything, rembg, etc.
96
+ foreground = RubyLLM.paint(
97
+ "Remove background from this image",
98
+ model: resolve_model,
99
+ image: image,
100
+ mode: "background_removal",
101
+ **build_removal_options
102
+ )
103
+
104
+ mask = nil
105
+ if resolve_return_mask
106
+ # Request mask separately if needed
107
+ begin
108
+ mask = RubyLLM.paint(
109
+ "Generate segmentation mask for this image",
110
+ model: resolve_model,
111
+ image: image,
112
+ mode: "segmentation_mask",
113
+ **build_removal_options
114
+ )
115
+ rescue StandardError
116
+ # Mask generation failed, continue without it
117
+ mask = nil
118
+ end
119
+ end
120
+
121
+ { foreground: foreground, mask: mask }
122
+ end
123
+
124
+ def build_removal_options
125
+ opts = {}
126
+ opts[:output_format] = resolve_output_format
127
+ opts[:refine_edges] = resolve_refine_edges if resolve_refine_edges
128
+ opts[:alpha_matting] = resolve_alpha_matting if resolve_alpha_matting
129
+ opts[:foreground_threshold] = resolve_foreground_threshold
130
+ opts[:background_threshold] = resolve_background_threshold
131
+ opts[:erode_size] = resolve_erode_size if resolve_erode_size > 0
132
+ opts[:return_mask] = resolve_return_mask if resolve_return_mask
133
+ opts[:assume_model_exists] = true if options[:assume_model_exists]
134
+ opts
135
+ end
136
+
137
+ def build_result(foreground:, mask:, started_at:, completed_at:)
138
+ BackgroundRemovalResult.new(
139
+ foreground: foreground,
140
+ mask: mask,
141
+ source_image: image,
142
+ model_id: resolve_model,
143
+ output_format: resolve_output_format,
144
+ alpha_matting: resolve_alpha_matting,
145
+ refine_edges: resolve_refine_edges,
146
+ started_at: started_at,
147
+ completed_at: completed_at,
148
+ tenant_id: @tenant_id,
149
+ remover_class: self.class.name
150
+ )
151
+ end
152
+
153
+ def build_error_result(error, started_at)
154
+ BackgroundRemovalResult.new(
155
+ foreground: nil,
156
+ mask: nil,
157
+ source_image: image,
158
+ model_id: resolve_model,
159
+ output_format: resolve_output_format,
160
+ alpha_matting: resolve_alpha_matting,
161
+ refine_edges: resolve_refine_edges,
162
+ started_at: started_at,
163
+ completed_at: Time.current,
164
+ tenant_id: @tenant_id,
165
+ remover_class: self.class.name,
166
+ error_class: error.class.name,
167
+ error_message: error.message
168
+ )
169
+ end
170
+
171
+ # Resolution methods
172
+
173
+ def resolve_output_format
174
+ options[:output_format] || self.class.output_format
175
+ end
176
+
177
+ def resolve_refine_edges
178
+ options.fetch(:refine_edges, self.class.refine_edges)
179
+ end
180
+
181
+ def resolve_alpha_matting
182
+ options.fetch(:alpha_matting, self.class.alpha_matting)
183
+ end
184
+
185
+ def resolve_foreground_threshold
186
+ options[:foreground_threshold] || self.class.foreground_threshold
187
+ end
188
+
189
+ def resolve_background_threshold
190
+ options[:background_threshold] || self.class.background_threshold
191
+ end
192
+
193
+ def resolve_erode_size
194
+ options[:erode_size] || self.class.erode_size
195
+ end
196
+
197
+ def resolve_return_mask
198
+ options.fetch(:return_mask, self.class.return_mask)
199
+ end
200
+
201
+ # Cache key components
202
+ def cache_key_components
203
+ [
204
+ "background_remover",
205
+ self.class.name,
206
+ self.class.version,
207
+ resolve_model,
208
+ resolve_output_format.to_s,
209
+ resolve_alpha_matting.to_s,
210
+ resolve_refine_edges.to_s,
211
+ resolve_foreground_threshold.to_s,
212
+ resolve_background_threshold.to_s,
213
+ Digest::SHA256.hexdigest(image_digest)
214
+ ]
215
+ end
216
+
217
+ def image_digest
218
+ if image.is_a?(String) && File.exist?(image)
219
+ File.read(image)
220
+ elsif image.respond_to?(:read)
221
+ content = image.read
222
+ image.rewind if image.respond_to?(:rewind)
223
+ content
224
+ else
225
+ image.to_s
226
+ end
227
+ end
228
+
229
+ def build_execution_metadata(result)
230
+ {
231
+ output_format: result.output_format,
232
+ alpha_matting: result.alpha_matting,
233
+ refine_edges: result.refine_edges,
234
+ has_mask: result.mask?
235
+ }
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "background_remover/dsl"
4
+ require_relative "background_remover/execution"
5
+
6
+ module RubyLLM
7
+ module Agents
8
+ # Background remover for subject extraction
9
+ #
10
+ # Removes backgrounds from images using segmentation models,
11
+ # producing transparent PNGs or masked outputs.
12
+ #
13
+ # @example Basic usage
14
+ # result = RubyLLM::Agents::BackgroundRemover.call(image: "path/to/photo.jpg")
15
+ # result.url # => "https://..." (transparent PNG)
16
+ # result.has_alpha? # => true
17
+ #
18
+ # @example Custom remover class
19
+ # class ProductBackgroundRemover < RubyLLM::Agents::BackgroundRemover
20
+ # model "segment-anything"
21
+ # output_format :png
22
+ # refine_edges true
23
+ # alpha_matting true
24
+ #
25
+ # description "Removes backgrounds from product photos"
26
+ # end
27
+ #
28
+ # result = ProductBackgroundRemover.call(image: product_photo)
29
+ # result.foreground # => The extracted subject
30
+ # result.mask # => Segmentation mask
31
+ #
32
+ class BackgroundRemover
33
+ extend DSL
34
+ include Execution
35
+
36
+ class << self
37
+ # Execute background removal
38
+ #
39
+ # @param image [String, IO] Path, URL, or IO object of the source image
40
+ # @param options [Hash] Additional options (model, output_format, etc.)
41
+ # @return [BackgroundRemovalResult] The result containing extracted subject
42
+ def call(image:, **options)
43
+ new(image: image, **options).call
44
+ end
45
+
46
+ # Ensure subclasses inherit DSL settings
47
+ def inherited(subclass)
48
+ super
49
+ subclass.instance_variable_set(:@model, @model)
50
+ subclass.instance_variable_set(:@output_format, @output_format)
51
+ subclass.instance_variable_set(:@refine_edges, @refine_edges)
52
+ subclass.instance_variable_set(:@alpha_matting, @alpha_matting)
53
+ subclass.instance_variable_set(:@foreground_threshold, @foreground_threshold)
54
+ subclass.instance_variable_set(:@background_threshold, @background_threshold)
55
+ subclass.instance_variable_set(:@erode_size, @erode_size)
56
+ subclass.instance_variable_set(:@return_mask, @return_mask)
57
+ subclass.instance_variable_set(:@version, @version)
58
+ subclass.instance_variable_set(:@description, @description)
59
+ subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
60
+ end
61
+ end
62
+
63
+ attr_reader :image, :options, :tenant_id
64
+
65
+ # Initialize a new background remover instance
66
+ #
67
+ # @param image [String, IO] Source image (path, URL, or IO object)
68
+ # @param options [Hash] Additional options
69
+ # @option options [String] :model Model to use
70
+ # @option options [Symbol] :output_format Output format (:png, :webp)
71
+ # @option options [Boolean] :refine_edges Enable edge refinement
72
+ # @option options [Boolean] :alpha_matting Enable alpha matting for better edges
73
+ # @option options [Boolean] :return_mask Also return the segmentation mask
74
+ # @option options [Object] :tenant Tenant for multi-tenancy
75
+ def initialize(image:, **options)
76
+ @image = image
77
+ @options = options
78
+ @tenant_id = nil
79
+ end
80
+
81
+ # Execute the background removal
82
+ #
83
+ # @return [BackgroundRemovalResult] The result containing extracted subject
84
+ def call
85
+ execute
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Agents
5
+ module Concerns
6
+ # Shared DSL methods for all image operation classes
7
+ #
8
+ # Provides common configuration options like model, version,
9
+ # description, and caching that are shared across ImageVariator,
10
+ # ImageEditor, ImageTransformer, and ImageUpscaler.
11
+ #
12
+ module ImageOperationDSL
13
+ # Set or get the model
14
+ #
15
+ # @param value [String, nil] Model identifier
16
+ # @return [String] The model to use
17
+ def model(value = nil)
18
+ if value
19
+ @model = value
20
+ else
21
+ @model || inherited_or_default(:model, default_model)
22
+ end
23
+ end
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
+ # Set or get the description
38
+ #
39
+ # @param value [String, nil] Description
40
+ # @return [String, nil] The description
41
+ def description(value = nil)
42
+ if value
43
+ @description = value
44
+ else
45
+ @description || inherited_or_default(:description, nil)
46
+ end
47
+ end
48
+
49
+ # Enable caching with the given TTL
50
+ #
51
+ # @param ttl [ActiveSupport::Duration, Integer] Cache duration
52
+ def cache_for(ttl)
53
+ @cache_ttl = ttl
54
+ end
55
+
56
+ # Get the cache TTL
57
+ #
58
+ # @return [ActiveSupport::Duration, Integer, nil] The cache TTL
59
+ def cache_ttl
60
+ @cache_ttl || inherited_or_default(:cache_ttl, nil)
61
+ end
62
+
63
+ # Check if caching is enabled
64
+ #
65
+ # @return [Boolean] true if caching is enabled
66
+ def cache_enabled?
67
+ !cache_ttl.nil?
68
+ end
69
+
70
+ private
71
+
72
+ def config
73
+ RubyLLM::Agents.configuration
74
+ end
75
+
76
+ def inherited_or_default(attribute, default)
77
+ if superclass.respond_to?(attribute)
78
+ superclass.public_send(attribute)
79
+ else
80
+ default
81
+ end
82
+ end
83
+
84
+ # Override in submodules to provide default model
85
+ def default_model
86
+ config.default_image_model
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module RubyLLM
6
+ module Agents
7
+ module Concerns
8
+ # Shared execution logic for all image operation classes
9
+ #
10
+ # Provides common functionality like tenant resolution, budget tracking,
11
+ # caching, and execution recording that are shared across ImageVariator,
12
+ # ImageEditor, ImageTransformer, and ImageUpscaler.
13
+ #
14
+ module ImageOperationExecution
15
+ private
16
+
17
+ # Resolve tenant from options
18
+ def resolve_tenant_context!
19
+ tenant = options[:tenant]
20
+ return unless tenant
21
+
22
+ @tenant_id = case tenant
23
+ when Hash then tenant[:id]
24
+ when Integer, String then tenant
25
+ else
26
+ tenant.try(:llm_tenant_id) || tenant.try(:id)
27
+ end
28
+ end
29
+
30
+ # Check budget before execution
31
+ def check_budget!
32
+ BudgetTracker.check!(
33
+ agent_type: self.class.name,
34
+ tenant_id: @tenant_id,
35
+ execution_type: execution_type
36
+ )
37
+ end
38
+
39
+ # Override in subclasses to specify execution type
40
+ def execution_type
41
+ raise NotImplementedError, "Subclasses must implement #execution_type"
42
+ end
43
+
44
+ # Caching support
45
+
46
+ def cache_enabled?
47
+ self.class.cache_enabled? && !options[:skip_cache]
48
+ end
49
+
50
+ def cache_key_components
51
+ raise NotImplementedError, "Subclasses must implement #cache_key_components"
52
+ end
53
+
54
+ def cache_key
55
+ components = ["ruby_llm_agents"] + cache_key_components
56
+ components.join(":")
57
+ end
58
+
59
+ def check_cache(result_class)
60
+ return nil unless defined?(Rails) && Rails.cache
61
+
62
+ cached_data = Rails.cache.read(cache_key)
63
+ return nil unless cached_data
64
+
65
+ result_class.from_cache(cached_data)
66
+ end
67
+
68
+ def write_cache(result)
69
+ return unless defined?(Rails) && Rails.cache
70
+ return unless result.success?
71
+
72
+ Rails.cache.write(cache_key, result.to_cache, expires_in: self.class.cache_ttl)
73
+ end
74
+
75
+ # Execution tracking
76
+
77
+ def execution_tracking_enabled?
78
+ config.track_image_generation
79
+ end
80
+
81
+ def record_execution(result)
82
+ return unless defined?(RubyLLM::Agents::Execution)
83
+
84
+ execution_data = build_execution_data(result)
85
+
86
+ if config.async_logging && defined?(ExecutionLoggerJob)
87
+ ExecutionLoggerJob.perform_later(execution_data)
88
+ else
89
+ RubyLLM::Agents::Execution.create!(execution_data)
90
+ end
91
+ rescue StandardError => e
92
+ Rails.logger.error("[RubyLLM::Agents] Failed to record #{execution_type} execution: #{e.message}") if defined?(Rails)
93
+ end
94
+
95
+ def record_failed_execution(error, started_at)
96
+ return unless defined?(RubyLLM::Agents::Execution)
97
+
98
+ execution_data = build_failed_execution_data(error, started_at)
99
+
100
+ if config.async_logging && defined?(ExecutionLoggerJob)
101
+ ExecutionLoggerJob.perform_later(execution_data)
102
+ else
103
+ RubyLLM::Agents::Execution.create!(execution_data)
104
+ end
105
+ rescue StandardError => e
106
+ Rails.logger.error("[RubyLLM::Agents] Failed to record failed #{execution_type} execution: #{e.message}") if defined?(Rails)
107
+ end
108
+
109
+ def build_execution_data(result)
110
+ {
111
+ agent_type: self.class.name,
112
+ tenant_id: @tenant_id,
113
+ execution_type: execution_type,
114
+ model_id: result.model_id,
115
+ status: "success",
116
+ input_tokens: 0,
117
+ output_tokens: 0,
118
+ total_cost: result.total_cost,
119
+ duration_ms: result.duration_ms,
120
+ started_at: result.started_at,
121
+ completed_at: result.completed_at,
122
+ metadata: build_execution_metadata(result)
123
+ }
124
+ end
125
+
126
+ def build_failed_execution_data(error, started_at)
127
+ {
128
+ agent_type: self.class.name,
129
+ tenant_id: @tenant_id,
130
+ execution_type: execution_type,
131
+ model_id: resolve_model,
132
+ status: "error",
133
+ input_tokens: 0,
134
+ output_tokens: 0,
135
+ total_cost: 0,
136
+ duration_ms: ((Time.current - started_at) * 1000).round,
137
+ started_at: started_at,
138
+ completed_at: Time.current,
139
+ error_class: error.class.name,
140
+ error_message: error.message.truncate(1000),
141
+ metadata: {}
142
+ }
143
+ end
144
+
145
+ def build_execution_metadata(result)
146
+ { count: result.count }
147
+ end
148
+
149
+ def budget_tracking_enabled?
150
+ config.budgets_enabled? && defined?(BudgetTracker)
151
+ end
152
+
153
+ def config
154
+ RubyLLM::Agents.configuration
155
+ end
156
+
157
+ # Model resolution with alias support
158
+ def resolve_model
159
+ model = options[:model] || self.class.model
160
+ config.image_model_aliases&.dig(model.to_sym) || model
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../concerns/image_operation_dsl"
4
+ require_relative "../generator/content_policy"
5
+
6
+ module RubyLLM
7
+ module Agents
8
+ class ImageEditor
9
+ # DSL for configuring image editors
10
+ #
11
+ # Provides class-level methods to configure model, size,
12
+ # and other image editing parameters.
13
+ #
14
+ # @example
15
+ # class ProductEditor < RubyLLM::Agents::ImageEditor
16
+ # model "gpt-image-1"
17
+ # size "1024x1024"
18
+ # content_policy :strict
19
+ # end
20
+ #
21
+ module DSL
22
+ include Concerns::ImageOperationDSL
23
+
24
+ # Set or get the output image size
25
+ #
26
+ # @param value [String, nil] Size (e.g., "1024x1024")
27
+ # @return [String] The size to use
28
+ def size(value = nil)
29
+ if value
30
+ @size = value
31
+ else
32
+ @size || inherited_or_default(:size, config.default_image_size)
33
+ end
34
+ end
35
+
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
+ private
49
+
50
+ def default_model
51
+ config.default_editor_model || config.default_image_model
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end