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,455 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
# Image generator base class for text-to-image generation using the middleware pipeline
|
|
8
|
+
#
|
|
9
|
+
# Follows the same patterns as other agents - inherits from BaseAgent for unified
|
|
10
|
+
# execution flow, caching, instrumentation, and budget controls through middleware.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# result = RubyLLM::Agents::ImageGenerator.call(prompt: "A sunset over mountains")
|
|
14
|
+
# result.url # => "https://..."
|
|
15
|
+
#
|
|
16
|
+
# @example Custom generator class
|
|
17
|
+
# class LogoGenerator < RubyLLM::Agents::ImageGenerator
|
|
18
|
+
# model "gpt-image-1"
|
|
19
|
+
# size "1024x1024"
|
|
20
|
+
# quality "hd"
|
|
21
|
+
# style "vivid"
|
|
22
|
+
#
|
|
23
|
+
# description "Generates company logos"
|
|
24
|
+
# content_policy :strict
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# result = LogoGenerator.call(prompt: "Minimalist tech company logo")
|
|
28
|
+
#
|
|
29
|
+
# @api public
|
|
30
|
+
class ImageGenerator < BaseAgent
|
|
31
|
+
class << self
|
|
32
|
+
# Returns the agent type for image generators
|
|
33
|
+
#
|
|
34
|
+
# @return [Symbol] :image
|
|
35
|
+
def agent_type
|
|
36
|
+
:image
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @!group Image-specific DSL
|
|
40
|
+
|
|
41
|
+
# Sets or returns the image generation model
|
|
42
|
+
#
|
|
43
|
+
# @param value [String, nil] Model identifier
|
|
44
|
+
# @return [String] The model to use
|
|
45
|
+
def model(value = nil)
|
|
46
|
+
@model = value if value
|
|
47
|
+
return @model if defined?(@model) && @model
|
|
48
|
+
|
|
49
|
+
if superclass.respond_to?(:agent_type) && superclass.agent_type == :image
|
|
50
|
+
superclass.model
|
|
51
|
+
else
|
|
52
|
+
default_image_model
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Sets or returns the image size
|
|
57
|
+
#
|
|
58
|
+
# @param value [String, nil] Size (e.g., "1024x1024", "1792x1024")
|
|
59
|
+
# @return [String] The size to use
|
|
60
|
+
def size(value = nil)
|
|
61
|
+
@size = value if value
|
|
62
|
+
@size || inherited_or_default(:size, default_image_size)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Sets or returns the quality level
|
|
66
|
+
#
|
|
67
|
+
# @param value [String, nil] Quality ("standard", "hd")
|
|
68
|
+
# @return [String] The quality to use
|
|
69
|
+
def quality(value = nil)
|
|
70
|
+
@quality = value if value
|
|
71
|
+
@quality || inherited_or_default(:quality, default_image_quality)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Sets or returns the style preset
|
|
75
|
+
#
|
|
76
|
+
# @param value [String, nil] Style ("vivid", "natural")
|
|
77
|
+
# @return [String] The style to use
|
|
78
|
+
def style(value = nil)
|
|
79
|
+
@style = value if value
|
|
80
|
+
@style || inherited_or_default(:style, default_image_style)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sets or returns the content policy level
|
|
84
|
+
#
|
|
85
|
+
# @param level [Symbol, nil] Policy level (:none, :standard, :moderate, :strict)
|
|
86
|
+
# @return [Symbol] The content policy level
|
|
87
|
+
def content_policy(level = nil)
|
|
88
|
+
@content_policy = level if level
|
|
89
|
+
@content_policy || inherited_or_default(:content_policy, :standard)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Sets or returns negative prompt (things to avoid in generation)
|
|
93
|
+
#
|
|
94
|
+
# @param value [String, nil] Negative prompt text
|
|
95
|
+
# @return [String, nil] The negative prompt
|
|
96
|
+
def negative_prompt(value = nil)
|
|
97
|
+
@negative_prompt = value if value
|
|
98
|
+
@negative_prompt || inherited_or_default(:negative_prompt, nil)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Sets or returns the seed for reproducible generation
|
|
102
|
+
#
|
|
103
|
+
# @param value [Integer, nil] Seed value
|
|
104
|
+
# @return [Integer, nil] The seed
|
|
105
|
+
def seed(value = nil)
|
|
106
|
+
@seed = value if value
|
|
107
|
+
@seed || inherited_or_default(:seed, nil)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Sets or returns guidance scale (CFG scale)
|
|
111
|
+
#
|
|
112
|
+
# @param value [Float, nil] Guidance scale (typically 1.0-20.0)
|
|
113
|
+
# @return [Float, nil] The guidance scale
|
|
114
|
+
def guidance_scale(value = nil)
|
|
115
|
+
@guidance_scale = value if value
|
|
116
|
+
@guidance_scale || inherited_or_default(:guidance_scale, nil)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Sets or returns number of inference steps
|
|
120
|
+
#
|
|
121
|
+
# @param value [Integer, nil] Number of steps
|
|
122
|
+
# @return [Integer, nil] The steps
|
|
123
|
+
def steps(value = nil)
|
|
124
|
+
@steps = value if value
|
|
125
|
+
@steps || inherited_or_default(:steps, nil)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Sets a prompt template (use {prompt} as placeholder)
|
|
129
|
+
#
|
|
130
|
+
# @param value [String, nil] Template string
|
|
131
|
+
# @return [String, nil] The template
|
|
132
|
+
def template(value = nil)
|
|
133
|
+
@template_string = value if value
|
|
134
|
+
@template_string || inherited_or_default(:template_string, nil)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Gets the template string
|
|
138
|
+
#
|
|
139
|
+
# @return [String, nil] The template string
|
|
140
|
+
def template_string
|
|
141
|
+
@template_string || inherited_or_default(:template_string, nil)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @!endgroup
|
|
145
|
+
|
|
146
|
+
# Factory method to execute image generation
|
|
147
|
+
#
|
|
148
|
+
# @param prompt [String] The text prompt for image generation
|
|
149
|
+
# @param options [Hash] Additional options
|
|
150
|
+
# @return [ImageGenerationResult] The result containing generated images
|
|
151
|
+
def call(prompt:, **options)
|
|
152
|
+
new(prompt: prompt, **options).call
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Ensure subclasses inherit DSL settings
|
|
156
|
+
def inherited(subclass)
|
|
157
|
+
super
|
|
158
|
+
subclass.instance_variable_set(:@model, @model)
|
|
159
|
+
subclass.instance_variable_set(:@size, @size)
|
|
160
|
+
subclass.instance_variable_set(:@quality, @quality)
|
|
161
|
+
subclass.instance_variable_set(:@style, @style)
|
|
162
|
+
subclass.instance_variable_set(:@version, @version)
|
|
163
|
+
subclass.instance_variable_set(:@description, @description)
|
|
164
|
+
subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
|
|
165
|
+
subclass.instance_variable_set(:@content_policy, @content_policy)
|
|
166
|
+
subclass.instance_variable_set(:@negative_prompt, @negative_prompt)
|
|
167
|
+
subclass.instance_variable_set(:@seed, @seed)
|
|
168
|
+
subclass.instance_variable_set(:@guidance_scale, @guidance_scale)
|
|
169
|
+
subclass.instance_variable_set(:@steps, @steps)
|
|
170
|
+
subclass.instance_variable_set(:@template_string, @template_string)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
def inherited_or_default(method, default)
|
|
176
|
+
superclass.respond_to?(method) ? superclass.send(method) : default
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def default_image_model
|
|
180
|
+
RubyLLM::Agents.configuration.default_image_model
|
|
181
|
+
rescue StandardError
|
|
182
|
+
"dall-e-3"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def default_image_size
|
|
186
|
+
RubyLLM::Agents.configuration.default_image_size
|
|
187
|
+
rescue StandardError
|
|
188
|
+
"1024x1024"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def default_image_quality
|
|
192
|
+
RubyLLM::Agents.configuration.default_image_quality
|
|
193
|
+
rescue StandardError
|
|
194
|
+
"standard"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def default_image_style
|
|
198
|
+
RubyLLM::Agents.configuration.default_image_style
|
|
199
|
+
rescue StandardError
|
|
200
|
+
"vivid"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# @!attribute [r] prompt
|
|
205
|
+
# @return [String] The text prompt for image generation
|
|
206
|
+
attr_reader :prompt
|
|
207
|
+
|
|
208
|
+
# Creates a new ImageGenerator instance
|
|
209
|
+
#
|
|
210
|
+
# @param prompt [String] The text prompt for image generation
|
|
211
|
+
# @param options [Hash] Additional options
|
|
212
|
+
def initialize(prompt:, **options)
|
|
213
|
+
@prompt = prompt
|
|
214
|
+
@runtime_count = options.delete(:count) || 1
|
|
215
|
+
|
|
216
|
+
# Set model to image model if not specified
|
|
217
|
+
options[:model] ||= self.class.model
|
|
218
|
+
|
|
219
|
+
super(**options)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Executes the image generation through the middleware pipeline
|
|
223
|
+
#
|
|
224
|
+
# @return [ImageGenerationResult] The result containing generated images
|
|
225
|
+
def call
|
|
226
|
+
context = build_context
|
|
227
|
+
result_context = Pipeline::Executor.execute(context)
|
|
228
|
+
result_context.output
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# The input for this generation operation
|
|
232
|
+
#
|
|
233
|
+
# @return [String] The prompt
|
|
234
|
+
def user_prompt
|
|
235
|
+
prompt
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Core image generation execution
|
|
239
|
+
#
|
|
240
|
+
# This is called by the Pipeline::Executor after middleware
|
|
241
|
+
# has been applied. Only contains the image generation API logic.
|
|
242
|
+
#
|
|
243
|
+
# @param context [Pipeline::Context] The execution context
|
|
244
|
+
# @return [void] Sets context.output with the ImageGenerationResult
|
|
245
|
+
def execute(context)
|
|
246
|
+
execution_started_at = Time.current
|
|
247
|
+
|
|
248
|
+
validate_prompt!
|
|
249
|
+
validate_content_policy!
|
|
250
|
+
|
|
251
|
+
# Generate image(s)
|
|
252
|
+
images = generate_images
|
|
253
|
+
|
|
254
|
+
execution_completed_at = Time.current
|
|
255
|
+
duration_ms = ((execution_completed_at - execution_started_at) * 1000).to_i
|
|
256
|
+
|
|
257
|
+
# Build result
|
|
258
|
+
result = build_result(
|
|
259
|
+
images: images,
|
|
260
|
+
started_at: context.started_at || execution_started_at,
|
|
261
|
+
completed_at: execution_completed_at,
|
|
262
|
+
tenant_id: context.tenant_id
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Update context with cost info
|
|
266
|
+
context.input_tokens = result.input_tokens
|
|
267
|
+
context.output_tokens = 0
|
|
268
|
+
context.total_cost = result.total_cost
|
|
269
|
+
|
|
270
|
+
context.output = result
|
|
271
|
+
rescue StandardError => e
|
|
272
|
+
execution_completed_at = Time.current
|
|
273
|
+
context.output = build_error_result(
|
|
274
|
+
e,
|
|
275
|
+
started_at: context.started_at || execution_started_at,
|
|
276
|
+
completed_at: execution_completed_at,
|
|
277
|
+
tenant_id: context.tenant_id
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Generates the cache key for this image generation
|
|
282
|
+
#
|
|
283
|
+
# @return [String] Cache key
|
|
284
|
+
def agent_cache_key
|
|
285
|
+
components = [
|
|
286
|
+
"ruby_llm_agents",
|
|
287
|
+
"image_generator",
|
|
288
|
+
self.class.name,
|
|
289
|
+
self.class.version,
|
|
290
|
+
resolved_model,
|
|
291
|
+
resolved_size,
|
|
292
|
+
resolved_quality,
|
|
293
|
+
resolved_style,
|
|
294
|
+
Digest::SHA256.hexdigest(prompt)
|
|
295
|
+
].compact
|
|
296
|
+
|
|
297
|
+
components.join("/")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
# Builds context for pipeline execution
|
|
303
|
+
#
|
|
304
|
+
# @return [Pipeline::Context] The context object
|
|
305
|
+
def build_context
|
|
306
|
+
Pipeline::Context.new(
|
|
307
|
+
input: user_prompt,
|
|
308
|
+
agent_class: self.class,
|
|
309
|
+
agent_instance: self,
|
|
310
|
+
model: resolved_model,
|
|
311
|
+
tenant: @options[:tenant],
|
|
312
|
+
skip_cache: @options[:skip_cache] || !single_image_request?
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Validates the prompt
|
|
317
|
+
def validate_prompt!
|
|
318
|
+
raise ArgumentError, "Prompt cannot be blank" if prompt.nil? || prompt.strip.empty?
|
|
319
|
+
|
|
320
|
+
max_length = config.max_image_prompt_length || 4000
|
|
321
|
+
if prompt.length > max_length
|
|
322
|
+
raise ArgumentError, "Prompt exceeds maximum length of #{max_length} characters"
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Validates prompt against content policy
|
|
327
|
+
def validate_content_policy!
|
|
328
|
+
policy = self.class.content_policy
|
|
329
|
+
return if policy == :none || policy == :standard
|
|
330
|
+
|
|
331
|
+
ContentPolicy.validate!(prompt, policy)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Generate images using RubyLLM.paint
|
|
335
|
+
def generate_images
|
|
336
|
+
count = @runtime_count
|
|
337
|
+
|
|
338
|
+
Array.new(count) do
|
|
339
|
+
paint_options = build_paint_options
|
|
340
|
+
RubyLLM.paint(apply_template(prompt), **paint_options)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Build options hash for RubyLLM.paint
|
|
345
|
+
def build_paint_options
|
|
346
|
+
opts = {
|
|
347
|
+
model: resolved_model,
|
|
348
|
+
size: resolved_size
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
opts[:quality] = resolved_quality if resolved_quality
|
|
352
|
+
opts[:style] = resolved_style if resolved_style
|
|
353
|
+
opts[:negative_prompt] = resolved_negative_prompt if resolved_negative_prompt
|
|
354
|
+
opts[:seed] = resolved_seed if resolved_seed
|
|
355
|
+
opts[:guidance_scale] = resolved_guidance_scale if resolved_guidance_scale
|
|
356
|
+
opts[:steps] = resolved_steps if resolved_steps
|
|
357
|
+
opts[:assume_model_exists] = true if @options[:assume_model_exists]
|
|
358
|
+
|
|
359
|
+
opts
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Apply prompt template if defined
|
|
363
|
+
def apply_template(text)
|
|
364
|
+
template = self.class.try(:template_string)
|
|
365
|
+
return text unless template
|
|
366
|
+
|
|
367
|
+
template.gsub("{prompt}", text)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Build successful result
|
|
371
|
+
def build_result(images:, started_at:, completed_at:, tenant_id:)
|
|
372
|
+
ImageGenerationResult.new(
|
|
373
|
+
images: images,
|
|
374
|
+
prompt: prompt,
|
|
375
|
+
model_id: resolved_model,
|
|
376
|
+
size: resolved_size,
|
|
377
|
+
quality: resolved_quality,
|
|
378
|
+
style: resolved_style,
|
|
379
|
+
started_at: started_at,
|
|
380
|
+
completed_at: completed_at,
|
|
381
|
+
tenant_id: tenant_id,
|
|
382
|
+
generator_class: self.class.name
|
|
383
|
+
)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Build error result
|
|
387
|
+
def build_error_result(error, started_at:, completed_at:, tenant_id:)
|
|
388
|
+
ImageGenerationResult.new(
|
|
389
|
+
images: [],
|
|
390
|
+
prompt: prompt,
|
|
391
|
+
model_id: resolved_model,
|
|
392
|
+
size: resolved_size,
|
|
393
|
+
quality: resolved_quality,
|
|
394
|
+
style: resolved_style,
|
|
395
|
+
started_at: started_at,
|
|
396
|
+
completed_at: completed_at,
|
|
397
|
+
tenant_id: tenant_id,
|
|
398
|
+
generator_class: self.class.name,
|
|
399
|
+
error_class: error.class.name,
|
|
400
|
+
error_message: error.message
|
|
401
|
+
)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Resolution methods (runtime options override class config)
|
|
405
|
+
|
|
406
|
+
def resolved_model
|
|
407
|
+
model = @options[:model] || @model || self.class.model
|
|
408
|
+
# Handle aliases
|
|
409
|
+
config.image_model_aliases&.dig(model.to_sym) || model
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def resolved_size
|
|
413
|
+
@options[:size] || self.class.size
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def resolved_quality
|
|
417
|
+
@options[:quality] || self.class.quality
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def resolved_style
|
|
421
|
+
@options[:style] || self.class.style
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def resolved_negative_prompt
|
|
425
|
+
@options[:negative_prompt] || self.class.negative_prompt
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def resolved_seed
|
|
429
|
+
@options[:seed] || self.class.seed
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def resolved_guidance_scale
|
|
433
|
+
@options[:guidance_scale] || self.class.guidance_scale
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def resolved_steps
|
|
437
|
+
@options[:steps] || self.class.steps
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def single_image_request?
|
|
441
|
+
@runtime_count == 1
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def config
|
|
445
|
+
RubyLLM::Agents.configuration
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Load supporting modules after class is defined (they reopen the class)
|
|
452
|
+
require_relative "generator/pricing"
|
|
453
|
+
require_relative "generator/content_policy"
|
|
454
|
+
require_relative "generator/templates"
|
|
455
|
+
require_relative "generator/active_storage_support"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
class ImagePipeline
|
|
6
|
+
# DSL for defining image pipeline steps and configuration
|
|
7
|
+
#
|
|
8
|
+
# Provides methods for configuring pipeline steps, callbacks,
|
|
9
|
+
# caching, and error handling behavior.
|
|
10
|
+
#
|
|
11
|
+
# @example Defining pipeline steps
|
|
12
|
+
# class MyPipeline < ImagePipeline
|
|
13
|
+
# step :generate, generator: LogoGenerator
|
|
14
|
+
# step :upscale, upscaler: PhotoUpscaler, scale: 4
|
|
15
|
+
# step :analyze, analyzer: ProductAnalyzer
|
|
16
|
+
#
|
|
17
|
+
# version "1.0"
|
|
18
|
+
# description "Complete product image pipeline"
|
|
19
|
+
# stop_on_error true
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
module DSL
|
|
23
|
+
# Define a pipeline step
|
|
24
|
+
#
|
|
25
|
+
# @param name [Symbol] Step name (must be unique)
|
|
26
|
+
# @param config [Hash] Step configuration
|
|
27
|
+
# @option config [Class] :generator ImageGenerator class for generation steps
|
|
28
|
+
# @option config [Class] :variator ImageVariator class for variation steps
|
|
29
|
+
# @option config [Class] :editor ImageEditor class for editing steps
|
|
30
|
+
# @option config [Class] :transformer ImageTransformer class for transformation steps
|
|
31
|
+
# @option config [Class] :upscaler ImageUpscaler class for upscaling steps
|
|
32
|
+
# @option config [Class] :analyzer ImageAnalyzer class for analysis steps
|
|
33
|
+
# @option config [Class] :remover BackgroundRemover class for background removal steps
|
|
34
|
+
# @option config [Proc] :if Conditional proc that receives context and returns boolean
|
|
35
|
+
# @option config [Proc] :unless Conditional proc that receives context and returns boolean
|
|
36
|
+
# @return [void]
|
|
37
|
+
#
|
|
38
|
+
# @example Different step types
|
|
39
|
+
# step :generate, generator: MyGenerator
|
|
40
|
+
# step :upscale, upscaler: MyUpscaler, scale: 2
|
|
41
|
+
# step :transform, transformer: StyleTransformer, strength: 0.7
|
|
42
|
+
# step :analyze, analyzer: ContentAnalyzer
|
|
43
|
+
# step :remove_bg, remover: BackgroundRemover
|
|
44
|
+
#
|
|
45
|
+
# @example Conditional steps
|
|
46
|
+
# step :upscale, upscaler: PhotoUpscaler, if: ->(ctx) { ctx[:high_quality] }
|
|
47
|
+
# step :remove_bg, remover: BackgroundRemover, unless: ->(ctx) { ctx[:keep_background] }
|
|
48
|
+
#
|
|
49
|
+
def step(name, **config)
|
|
50
|
+
@steps ||= []
|
|
51
|
+
|
|
52
|
+
# Validate step configuration
|
|
53
|
+
validate_step_config!(name, config)
|
|
54
|
+
|
|
55
|
+
@steps << {
|
|
56
|
+
name: name,
|
|
57
|
+
config: config,
|
|
58
|
+
type: determine_step_type(config)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get all defined steps
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<Hash>] Array of step definitions
|
|
65
|
+
def steps
|
|
66
|
+
@steps ||= []
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add a callback to run before the pipeline
|
|
70
|
+
#
|
|
71
|
+
# @param method_name [Symbol] Method to call
|
|
72
|
+
# @yield Block to execute
|
|
73
|
+
# @return [void]
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# before_pipeline :validate_inputs
|
|
77
|
+
# before_pipeline { |ctx| ctx[:started_at] = Time.current }
|
|
78
|
+
#
|
|
79
|
+
def before_pipeline(method_name = nil, &block)
|
|
80
|
+
@callbacks ||= { before: [], after: [] }
|
|
81
|
+
@callbacks[:before] << (block || method_name)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Add a callback to run after the pipeline
|
|
85
|
+
#
|
|
86
|
+
# @param method_name [Symbol] Method to call
|
|
87
|
+
# @yield Block to execute
|
|
88
|
+
# @return [void]
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# after_pipeline :add_watermark
|
|
92
|
+
# after_pipeline { |result| notify_completion(result) }
|
|
93
|
+
#
|
|
94
|
+
def after_pipeline(method_name = nil, &block)
|
|
95
|
+
@callbacks ||= { before: [], after: [] }
|
|
96
|
+
@callbacks[:after] << (block || method_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Get callbacks
|
|
100
|
+
#
|
|
101
|
+
# @return [Hash] Hash with :before and :after arrays
|
|
102
|
+
def callbacks
|
|
103
|
+
@callbacks ||= { before: [], after: [] }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Set or get the version
|
|
107
|
+
#
|
|
108
|
+
# @param value [String, nil] Version identifier
|
|
109
|
+
# @return [String] The version
|
|
110
|
+
def version(value = nil)
|
|
111
|
+
if value
|
|
112
|
+
@version = value
|
|
113
|
+
else
|
|
114
|
+
@version || inherited_or_default(:version, "v1")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Set or get the description
|
|
119
|
+
#
|
|
120
|
+
# @param value [String, nil] Description
|
|
121
|
+
# @return [String, nil] The description
|
|
122
|
+
def description(value = nil)
|
|
123
|
+
if value
|
|
124
|
+
@description = value
|
|
125
|
+
else
|
|
126
|
+
@description || inherited_or_default(:description, nil)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Enable caching with the given TTL
|
|
131
|
+
#
|
|
132
|
+
# @param ttl [ActiveSupport::Duration, Integer] Cache duration
|
|
133
|
+
def cache_for(ttl)
|
|
134
|
+
@cache_ttl = ttl
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Get the cache TTL
|
|
138
|
+
#
|
|
139
|
+
# @return [ActiveSupport::Duration, Integer, nil] The cache TTL
|
|
140
|
+
def cache_ttl
|
|
141
|
+
@cache_ttl || inherited_or_default(:cache_ttl, nil)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Check if caching is enabled
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean] true if caching is enabled
|
|
147
|
+
def cache_enabled?
|
|
148
|
+
!cache_ttl.nil?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Set whether to stop on error (default: true)
|
|
152
|
+
#
|
|
153
|
+
# @param value [Boolean, nil] Whether to stop on first error
|
|
154
|
+
# @return [Boolean] Current setting
|
|
155
|
+
def stop_on_error(value = nil)
|
|
156
|
+
if value.nil?
|
|
157
|
+
return @stop_on_error if defined?(@stop_on_error) && !@stop_on_error.nil?
|
|
158
|
+
inherited_or_default(:stop_on_error, true)
|
|
159
|
+
else
|
|
160
|
+
@stop_on_error = value
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
alias stop_on_error? stop_on_error
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def validate_step_config!(name, config)
|
|
169
|
+
# Check for duplicate step names
|
|
170
|
+
if @steps&.any? { |s| s[:name] == name }
|
|
171
|
+
raise ArgumentError, "Step :#{name} is already defined"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Check for valid step type
|
|
175
|
+
valid_keys = %i[generator variator editor transformer upscaler analyzer remover]
|
|
176
|
+
step_keys = config.keys & valid_keys
|
|
177
|
+
|
|
178
|
+
if step_keys.empty?
|
|
179
|
+
raise ArgumentError, "Step :#{name} must specify one of: #{valid_keys.join(', ')}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if step_keys.size > 1
|
|
183
|
+
raise ArgumentError, "Step :#{name} can only specify one step type, got: #{step_keys.join(', ')}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Validate the class responds to call
|
|
187
|
+
step_class = config[step_keys.first]
|
|
188
|
+
unless step_class.respond_to?(:call)
|
|
189
|
+
raise ArgumentError, "#{step_class} must respond to .call"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def determine_step_type(config)
|
|
194
|
+
%i[generator variator editor transformer upscaler analyzer remover].find do |type|
|
|
195
|
+
config.key?(type)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def inherited_or_default(attribute, default)
|
|
200
|
+
if superclass.respond_to?(attribute)
|
|
201
|
+
superclass.public_send(attribute)
|
|
202
|
+
else
|
|
203
|
+
default
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def config
|
|
208
|
+
RubyLLM::Agents.configuration
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|