ruby_llm-agents 0.5.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.
- checksums.yaml +4 -4
- data/README.md +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +172 -34
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -366
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,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
|