ruby_llm-agents 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +172 -34
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -366
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Analyzer < ApplicationImageAnalyzer
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Analyzer < ApplicationImageAnalyzer
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
analysis_type :<%= options[:analysis_type] %>
|
|
16
|
+
<% if options[:extract_colors] -%>
|
|
17
|
+
extract_colors true
|
|
18
|
+
<% end -%>
|
|
19
|
+
<% if options[:detect_objects] -%>
|
|
20
|
+
detect_objects true
|
|
21
|
+
<% end -%>
|
|
22
|
+
<% if options[:extract_text] -%>
|
|
23
|
+
extract_text true
|
|
24
|
+
<% end -%>
|
|
25
|
+
max_tags <%= options[:max_tags] %>
|
|
26
|
+
<% if options[:cache] -%>
|
|
27
|
+
|
|
28
|
+
# Caching
|
|
29
|
+
cache_for <%= options[:cache] %>
|
|
30
|
+
<% end -%>
|
|
31
|
+
|
|
32
|
+
# Optional: Custom analysis prompt
|
|
33
|
+
# prompt "Analyze this image and describe..."
|
|
34
|
+
|
|
35
|
+
# Optional: Description
|
|
36
|
+
# description "Analyzes <%= class_name.downcase %> images"
|
|
37
|
+
<%- if class_name.include?("::") -%>
|
|
38
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
39
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
40
|
+
<%- end -%>
|
|
41
|
+
<%- else -%>
|
|
42
|
+
end
|
|
43
|
+
<%- end -%>
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Editor < ApplicationImageEditor
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Editor < ApplicationImageEditor
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
size "<%= options[:size] %>"
|
|
16
|
+
<% if options[:content_policy] != "standard" -%>
|
|
17
|
+
content_policy :<%= options[:content_policy] %>
|
|
18
|
+
<% end -%>
|
|
19
|
+
<% if options[:cache] -%>
|
|
20
|
+
|
|
21
|
+
# Caching
|
|
22
|
+
cache_for <%= options[:cache] %>
|
|
23
|
+
<% end -%>
|
|
24
|
+
|
|
25
|
+
# Optional: Description
|
|
26
|
+
# description "Edits <%= class_name.downcase %> images"
|
|
27
|
+
<%- if class_name.include?("::") -%>
|
|
28
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
29
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
30
|
+
<%- end -%>
|
|
31
|
+
<%- else -%>
|
|
32
|
+
end
|
|
33
|
+
<%- end -%>
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Generator < ApplicationImageGenerator
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Generator < ApplicationImageGenerator
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
size "<%= options[:size] %>"
|
|
16
|
+
quality "<%= options[:quality] %>"
|
|
17
|
+
style "<%= options[:style] %>"
|
|
18
|
+
<% if options[:content_policy] != "standard" -%>
|
|
19
|
+
content_policy :<%= options[:content_policy] %>
|
|
20
|
+
<% end -%>
|
|
21
|
+
<% if options[:cache] -%>
|
|
22
|
+
|
|
23
|
+
# Caching
|
|
24
|
+
cache_for <%= options[:cache] %>
|
|
25
|
+
<% end -%>
|
|
26
|
+
|
|
27
|
+
# Optional: Add a prompt template
|
|
28
|
+
# template "Professional {prompt}, high quality, detailed"
|
|
29
|
+
|
|
30
|
+
# Optional: Add negative prompts (for models that support it)
|
|
31
|
+
# negative_prompt "blurry, low quality, distorted"
|
|
32
|
+
|
|
33
|
+
# Optional: Custom preprocessing
|
|
34
|
+
# Override this method to modify prompts before generation
|
|
35
|
+
#
|
|
36
|
+
# def preprocess_prompt(prompt)
|
|
37
|
+
# "#{prompt}, professional quality"
|
|
38
|
+
# end
|
|
39
|
+
<%- if class_name.include?("::") -%>
|
|
40
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
41
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
42
|
+
<%- end -%>
|
|
43
|
+
<%- else -%>
|
|
44
|
+
end
|
|
45
|
+
<%- end -%>
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Pipeline < ApplicationImagePipeline
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Pipeline < ApplicationImagePipeline
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Pipeline steps
|
|
14
|
+
<%- parsed_steps.each do |step| -%>
|
|
15
|
+
<%- case step -%>
|
|
16
|
+
<%- when :generate -%>
|
|
17
|
+
step :<%= step %>, generator: <%= class_name %>Generator
|
|
18
|
+
<%- when :upscale -%>
|
|
19
|
+
step :<%= step %>, upscaler: <%= class_name %>Upscaler
|
|
20
|
+
<%- when :transform -%>
|
|
21
|
+
step :<%= step %>, transformer: <%= class_name %>Transformer
|
|
22
|
+
<%- when :analyze -%>
|
|
23
|
+
step :<%= step %>, analyzer: <%= class_name %>Analyzer
|
|
24
|
+
<%- when :remove_background -%>
|
|
25
|
+
step :<%= step %>, remover: <%= class_name %>BackgroundRemover
|
|
26
|
+
<%- else -%>
|
|
27
|
+
# step :<%= step %>, ...: <%= class_name %><%= step.to_s.camelize %>
|
|
28
|
+
<%- end -%>
|
|
29
|
+
<%- end -%>
|
|
30
|
+
<%- if options[:stop_on_error] == false -%>
|
|
31
|
+
|
|
32
|
+
stop_on_error false
|
|
33
|
+
<%- end -%>
|
|
34
|
+
<% if options[:cache] -%>
|
|
35
|
+
|
|
36
|
+
# Caching
|
|
37
|
+
cache_for <%= options[:cache] %>
|
|
38
|
+
<% end -%>
|
|
39
|
+
|
|
40
|
+
description "<%= class_name %> image processing pipeline"
|
|
41
|
+
version "1.0"
|
|
42
|
+
<%- if class_name.include?("::") -%>
|
|
43
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
44
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
45
|
+
<%- end -%>
|
|
46
|
+
<%- else -%>
|
|
47
|
+
end
|
|
48
|
+
<%- end -%>
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Transformer < ApplicationImageTransformer
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Transformer < ApplicationImageTransformer
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
size "<%= options[:size] %>"
|
|
16
|
+
strength <%= options[:strength] %>
|
|
17
|
+
<% if options[:content_policy] != "standard" -%>
|
|
18
|
+
content_policy :<%= options[:content_policy] %>
|
|
19
|
+
<% end -%>
|
|
20
|
+
<% if options[:template] -%>
|
|
21
|
+
|
|
22
|
+
# Prompt template
|
|
23
|
+
template "<%= options[:template] %>"
|
|
24
|
+
<% end -%>
|
|
25
|
+
<% if options[:cache] -%>
|
|
26
|
+
|
|
27
|
+
# Caching
|
|
28
|
+
cache_for <%= options[:cache] %>
|
|
29
|
+
<% end -%>
|
|
30
|
+
|
|
31
|
+
# Optional: Negative prompts
|
|
32
|
+
# negative_prompt "blurry, low quality, distorted"
|
|
33
|
+
|
|
34
|
+
# Optional: Description
|
|
35
|
+
# description "Transforms images into <%= class_name.downcase %> style"
|
|
36
|
+
<%- if class_name.include?("::") -%>
|
|
37
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
38
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
39
|
+
<%- end -%>
|
|
40
|
+
<%- else -%>
|
|
41
|
+
end
|
|
42
|
+
<%- end -%>
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Upscaler < ApplicationImageUpscaler
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Upscaler < ApplicationImageUpscaler
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
scale <%= options[:scale] %>
|
|
16
|
+
<% if options[:face_enhance] -%>
|
|
17
|
+
face_enhance true
|
|
18
|
+
<% end -%>
|
|
19
|
+
<% if options[:cache] -%>
|
|
20
|
+
|
|
21
|
+
# Caching
|
|
22
|
+
cache_for <%= options[:cache] %>
|
|
23
|
+
<% end -%>
|
|
24
|
+
|
|
25
|
+
# Optional: Denoise strength (0.0-1.0)
|
|
26
|
+
# denoise_strength 0.5
|
|
27
|
+
|
|
28
|
+
# Optional: Description
|
|
29
|
+
# description "Upscales <%= class_name.downcase %> images"
|
|
30
|
+
<%- if class_name.include?("::") -%>
|
|
31
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
32
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
33
|
+
<%- end -%>
|
|
34
|
+
<%- else -%>
|
|
35
|
+
end
|
|
36
|
+
<%- end -%>
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module <%= @root_namespace %>
|
|
4
|
+
module Image
|
|
5
|
+
<%- if class_name.include?("::") -%>
|
|
6
|
+
<%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
|
|
7
|
+
<%= " " * (i + 2) %>module <%= mod %>
|
|
8
|
+
<%- end -%>
|
|
9
|
+
<%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Variator < ApplicationImageVariator
|
|
10
|
+
<%- else -%>
|
|
11
|
+
class <%= class_name %>Variator < ApplicationImageVariator
|
|
12
|
+
<%- end -%>
|
|
13
|
+
# Model configuration
|
|
14
|
+
model "<%= options[:model] %>"
|
|
15
|
+
size "<%= options[:size] %>"
|
|
16
|
+
variation_strength <%= options[:variation_strength] %>
|
|
17
|
+
<% if options[:cache] -%>
|
|
18
|
+
|
|
19
|
+
# Caching
|
|
20
|
+
cache_for <%= options[:cache] %>
|
|
21
|
+
<% end -%>
|
|
22
|
+
|
|
23
|
+
# Optional: Description
|
|
24
|
+
# description "Creates variations of <%= class_name.downcase %> images"
|
|
25
|
+
<%- if class_name.include?("::") -%>
|
|
26
|
+
<%- (class_name.split("::").length + 1).times do |i| -%>
|
|
27
|
+
<%= " " * (class_name.split("::").length + 1 - i) %>end
|
|
28
|
+
<%- end -%>
|
|
29
|
+
<%- else -%>
|
|
30
|
+
end
|
|
31
|
+
<%- end -%>
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# <%= @root_namespace %> Agents
|
|
2
|
+
|
|
3
|
+
This directory contains LLM-powered agents for the application. All agents inherit from `ApplicationAgent`.
|
|
4
|
+
|
|
5
|
+
## Creating a New Agent
|
|
6
|
+
|
|
7
|
+
Use the generator:
|
|
8
|
+
```bash
|
|
9
|
+
rails generate ruby_llm_agents:agent AgentName param1:required param2:default_value
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or create manually by extending `ApplicationAgent`:
|
|
13
|
+
```ruby
|
|
14
|
+
module <%= @root_namespace %>
|
|
15
|
+
class MyAgent < ApplicationAgent
|
|
16
|
+
# Configuration
|
|
17
|
+
model "gpt-4o"
|
|
18
|
+
temperature 0.0
|
|
19
|
+
version "1.0"
|
|
20
|
+
|
|
21
|
+
# Parameters
|
|
22
|
+
param :query, required: true
|
|
23
|
+
param :limit, default: 10
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def system_prompt
|
|
28
|
+
"You are a helpful assistant."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def user_prompt
|
|
32
|
+
query
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## DSL Reference
|
|
39
|
+
|
|
40
|
+
### Model Configuration
|
|
41
|
+
|
|
42
|
+
| Method | Description | Example |
|
|
43
|
+
|--------|-------------|---------|
|
|
44
|
+
| `model` | LLM model to use | `model "gpt-4o"` |
|
|
45
|
+
| `temperature` | Response randomness (0.0-2.0) | `temperature 0.7` |
|
|
46
|
+
| `version` | Cache invalidation version | `version "2.0"` |
|
|
47
|
+
| `timeout` | Request timeout in seconds | `timeout 30` |
|
|
48
|
+
| `description` | Human-readable description | `description "Searches documents"` |
|
|
49
|
+
|
|
50
|
+
### Parameters
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
param :name # Optional parameter
|
|
54
|
+
param :query, required: true # Required parameter
|
|
55
|
+
param :limit, default: 10 # With default value
|
|
56
|
+
param :count, type: Integer # With type validation
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Access parameters as methods: `query`, `limit`, etc.
|
|
60
|
+
|
|
61
|
+
### Caching
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
cache 1.hour # Enable with TTL
|
|
65
|
+
cache_for 30.minutes # Alias for cache
|
|
66
|
+
|
|
67
|
+
cache_key_includes :user_id, :query # Only these params in cache key
|
|
68
|
+
cache_key_excludes :timestamp # Exclude from cache key
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Reliability (Retries & Fallbacks)
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Individual settings
|
|
75
|
+
retries max: 3, backoff: :exponential, base: 0.4, max_delay: 3.0
|
|
76
|
+
fallback_models "gpt-4o-mini", "claude-3-haiku"
|
|
77
|
+
total_timeout 30
|
|
78
|
+
circuit_breaker errors: 5, within: 60, cooldown: 300
|
|
79
|
+
|
|
80
|
+
# Or grouped in a block
|
|
81
|
+
reliability do
|
|
82
|
+
retries max: 3, backoff: :exponential
|
|
83
|
+
fallback_models "gpt-4o-mini"
|
|
84
|
+
total_timeout 30
|
|
85
|
+
circuit_breaker errors: 5, within: 60
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Streaming
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
streaming true # Enable streaming by default
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Tools
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
tools [SearchTool, CalculatorTool] # Make tools available to agent
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Extended Thinking
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
thinking effort: :high # Enable extended thinking
|
|
105
|
+
thinking budget: 10000 # With token budget
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Moderation
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
moderation :input # Check input before LLM call
|
|
112
|
+
moderation :output # Check output after LLM call
|
|
113
|
+
moderation :both # Check both
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Required Methods
|
|
117
|
+
|
|
118
|
+
### `user_prompt` (required)
|
|
119
|
+
The prompt sent to the LLM. Must return a String.
|
|
120
|
+
|
|
121
|
+
### `system_prompt` (optional)
|
|
122
|
+
Instructions for the LLM. Return nil for no system prompt.
|
|
123
|
+
|
|
124
|
+
## Optional Overrides
|
|
125
|
+
|
|
126
|
+
### `schema`
|
|
127
|
+
Return a `RubyLLM::Schema` for structured JSON output:
|
|
128
|
+
```ruby
|
|
129
|
+
def schema
|
|
130
|
+
@schema ||= RubyLLM::Schema.create do
|
|
131
|
+
string :result, description: "The result"
|
|
132
|
+
integer :confidence, description: "Confidence 1-100"
|
|
133
|
+
array :tags do
|
|
134
|
+
string
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `process_response(response)`
|
|
141
|
+
Transform the LLM response before returning:
|
|
142
|
+
```ruby
|
|
143
|
+
def process_response(response)
|
|
144
|
+
content = response.content
|
|
145
|
+
# Custom processing
|
|
146
|
+
content
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `messages`
|
|
151
|
+
Provide conversation history for multi-turn:
|
|
152
|
+
```ruby
|
|
153
|
+
def messages
|
|
154
|
+
[
|
|
155
|
+
{ role: :user, content: "Previous question" },
|
|
156
|
+
{ role: :assistant, content: "Previous answer" }
|
|
157
|
+
]
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `cache_key_data`
|
|
162
|
+
Customize what goes into the cache key:
|
|
163
|
+
```ruby
|
|
164
|
+
def cache_key_data
|
|
165
|
+
{ query: query, locale: I18n.locale }
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `execution_metadata`
|
|
170
|
+
Add custom data to execution logs:
|
|
171
|
+
```ruby
|
|
172
|
+
def execution_metadata
|
|
173
|
+
{ request_id: params[:request_id] }
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Calling Agents
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# Basic call
|
|
181
|
+
result = <%= @root_namespace %>::MyAgent.call(query: "hello")
|
|
182
|
+
|
|
183
|
+
# Access result
|
|
184
|
+
result.content # The response content
|
|
185
|
+
result.input_tokens # Tokens used in prompt
|
|
186
|
+
result.output_tokens # Tokens in response
|
|
187
|
+
result.total_cost # Cost in USD
|
|
188
|
+
|
|
189
|
+
# Debug mode (no API call)
|
|
190
|
+
result = <%= @root_namespace %>::MyAgent.call(query: "hello", dry_run: true)
|
|
191
|
+
|
|
192
|
+
# Skip cache
|
|
193
|
+
result = <%= @root_namespace %>::MyAgent.call(query: "hello", skip_cache: true)
|
|
194
|
+
|
|
195
|
+
# With attachments
|
|
196
|
+
result = <%= @root_namespace %>::MyAgent.call(query: "describe this", with: "image.png")
|
|
197
|
+
|
|
198
|
+
# Streaming
|
|
199
|
+
<%= @root_namespace %>::MyAgent.stream(query: "hello") do |chunk|
|
|
200
|
+
print chunk.content
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Multi-tenancy
|
|
204
|
+
result = <%= @root_namespace %>::MyAgent.call(query: "hello", tenant: current_user)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Testing Agents
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
RSpec.describe <%= @root_namespace %>::MyAgent do
|
|
211
|
+
describe ".call" do
|
|
212
|
+
it "returns expected result" do
|
|
213
|
+
# Use dry_run for unit tests
|
|
214
|
+
result = described_class.call(query: "test", dry_run: true)
|
|
215
|
+
expect(result.content[:user_prompt]).to eq("test")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Best Practices
|
|
222
|
+
|
|
223
|
+
1. **Keep prompts focused** - One agent, one task
|
|
224
|
+
2. **Use structured output** - Define schemas for predictable responses
|
|
225
|
+
3. **Enable caching** - For deterministic queries
|
|
226
|
+
4. **Set appropriate temperatures** - 0.0 for deterministic, higher for creative
|
|
227
|
+
5. **Configure retries** - For production reliability
|
|
228
|
+
6. **Version your agents** - Bump version when changing prompts
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# <%= @root_namespace %>::Image Background Removers
|
|
2
|
+
|
|
3
|
+
This directory contains background removal services. All removers inherit from `ApplicationBackgroundRemover`.
|
|
4
|
+
|
|
5
|
+
## Creating a New Remover
|
|
6
|
+
|
|
7
|
+
Use the generator:
|
|
8
|
+
```bash
|
|
9
|
+
rails generate ruby_llm_agents:background_remover RemoverName
|
|
10
|
+
rails generate ruby_llm_agents:background_remover Product --edge_refinement high
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or create manually:
|
|
14
|
+
```ruby
|
|
15
|
+
module <%= @root_namespace %>
|
|
16
|
+
module Image
|
|
17
|
+
class ProductRemover < ApplicationBackgroundRemover
|
|
18
|
+
model "remove-bg"
|
|
19
|
+
edge_refinement :high
|
|
20
|
+
output_format :png
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## DSL Reference
|
|
27
|
+
|
|
28
|
+
| Method | Description | Example |
|
|
29
|
+
|--------|-------------|---------|
|
|
30
|
+
| `model` | Background removal model | `model "remove-bg"` |
|
|
31
|
+
| `edge_refinement` | Edge quality | `edge_refinement :high` |
|
|
32
|
+
| `output_format` | Output format | `output_format :png` |
|
|
33
|
+
| `background_color` | Replace with color | `background_color "#FFFFFF"` |
|
|
34
|
+
|
|
35
|
+
### Edge Refinement
|
|
36
|
+
- `:low` - Fastest, rough edges
|
|
37
|
+
- `:medium` - Balanced (default)
|
|
38
|
+
- `:high` - Slow, clean edges
|
|
39
|
+
|
|
40
|
+
### Output Formats
|
|
41
|
+
- `:png` - Transparent background (default)
|
|
42
|
+
- `:jpg` - With background color
|
|
43
|
+
- `:webp` - Modern format, smaller size
|
|
44
|
+
|
|
45
|
+
## Using Removers
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
result = <%= @root_namespace %>::Image::ProductRemover.call(image: "product.jpg")
|
|
49
|
+
|
|
50
|
+
result.url # Image URL with transparent background
|
|
51
|
+
result.image_data # PNG binary data
|
|
52
|
+
result.has_alpha? # true (has transparency)
|
|
53
|
+
result.save("product_nobg.png")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### With Background Color
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
result = <%= @root_namespace %>::Image::ProductRemover.call(
|
|
60
|
+
image: "product.jpg",
|
|
61
|
+
background_color: "#FFFFFF" # White background
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Override Settings
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
result = <%= @root_namespace %>::Image::ProductRemover.call(
|
|
69
|
+
image: "complex_photo.jpg",
|
|
70
|
+
edge_refinement: :high,
|
|
71
|
+
output_format: :webp
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Use Cases
|
|
76
|
+
|
|
77
|
+
### E-commerce Product Photos
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
module <%= @root_namespace %>
|
|
81
|
+
module Image
|
|
82
|
+
class ProductBackgroundRemover < ApplicationBackgroundRemover
|
|
83
|
+
edge_refinement :high
|
|
84
|
+
output_format :png
|
|
85
|
+
|
|
86
|
+
cache_for 30.days # Product images don't change often
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Profile Photo Processing
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
module <%= @root_namespace %>
|
|
96
|
+
module Image
|
|
97
|
+
class AvatarBackgroundRemover < ApplicationBackgroundRemover
|
|
98
|
+
edge_refinement :high
|
|
99
|
+
output_format :png
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Remove background and resize
|
|
105
|
+
result = <%= @root_namespace %>::Image::AvatarBackgroundRemover.call(image: uploaded_photo)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Batch Processing
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
Product.where(background_removed: false).find_each do |product|
|
|
112
|
+
result = <%= @root_namespace %>::Image::ProductBackgroundRemover.call(
|
|
113
|
+
image: product.original_image.download
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
product.processed_image.attach(
|
|
117
|
+
io: StringIO.new(result.image_data),
|
|
118
|
+
filename: "#{product.id}_nobg.png",
|
|
119
|
+
content_type: "image/png"
|
|
120
|
+
)
|
|
121
|
+
product.update!(background_removed: true)
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Best Practices
|
|
126
|
+
|
|
127
|
+
1. **Use high edge refinement for products** - Clean edges look professional
|
|
128
|
+
2. **PNG for transparency** - JPG doesn't support alpha
|
|
129
|
+
3. **Cache aggressively** - Same image = same result
|
|
130
|
+
4. **Consider file size** - PNG is larger than JPG
|
|
131
|
+
5. **Test edge cases** - Complex backgrounds, similar colors
|