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,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Result wrapper for image editing operations
|
|
6
|
+
#
|
|
7
|
+
# Provides a consistent interface for accessing edited images,
|
|
8
|
+
# metadata, timing, and cost information.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing edited image
|
|
11
|
+
# result = ImageEditor.call(
|
|
12
|
+
# image: "photo.png",
|
|
13
|
+
# mask: "mask.png",
|
|
14
|
+
# prompt: "Replace background"
|
|
15
|
+
# )
|
|
16
|
+
# result.url # => "https://..."
|
|
17
|
+
# result.success? # => true
|
|
18
|
+
#
|
|
19
|
+
class ImageEditResult
|
|
20
|
+
attr_reader :images, :source_image, :mask, :prompt, :model_id, :size,
|
|
21
|
+
:started_at, :completed_at, :tenant_id, :editor_class,
|
|
22
|
+
:error_class, :error_message
|
|
23
|
+
|
|
24
|
+
# Initialize a new result
|
|
25
|
+
#
|
|
26
|
+
# @param images [Array<Object>] Array of edited image objects
|
|
27
|
+
# @param source_image [String] The original source image
|
|
28
|
+
# @param mask [String] The mask image used
|
|
29
|
+
# @param prompt [String] The edit prompt
|
|
30
|
+
# @param model_id [String] Model used for editing
|
|
31
|
+
# @param size [String] Image size
|
|
32
|
+
# @param started_at [Time] When editing started
|
|
33
|
+
# @param completed_at [Time] When editing completed
|
|
34
|
+
# @param tenant_id [String, nil] Tenant identifier
|
|
35
|
+
# @param editor_class [String] Name of the editor class
|
|
36
|
+
# @param error_class [String, nil] Error class name if failed
|
|
37
|
+
# @param error_message [String, nil] Error message if failed
|
|
38
|
+
def initialize(images:, source_image:, mask:, prompt:, model_id:, size:,
|
|
39
|
+
started_at:, completed_at:, tenant_id:, editor_class:,
|
|
40
|
+
error_class: nil, error_message: nil)
|
|
41
|
+
@images = images
|
|
42
|
+
@source_image = source_image
|
|
43
|
+
@mask = mask
|
|
44
|
+
@prompt = prompt
|
|
45
|
+
@model_id = model_id
|
|
46
|
+
@size = size
|
|
47
|
+
@started_at = started_at
|
|
48
|
+
@completed_at = completed_at
|
|
49
|
+
@tenant_id = tenant_id
|
|
50
|
+
@editor_class = editor_class
|
|
51
|
+
@error_class = error_class
|
|
52
|
+
@error_message = error_message
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Status helpers
|
|
56
|
+
|
|
57
|
+
def success?
|
|
58
|
+
error_class.nil? && images.any?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def error?
|
|
62
|
+
!success?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def single?
|
|
66
|
+
count == 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def batch?
|
|
70
|
+
count > 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Image access
|
|
74
|
+
|
|
75
|
+
def image
|
|
76
|
+
images.first
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def url
|
|
80
|
+
image&.url
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def urls
|
|
84
|
+
images.map(&:url).compact
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def data
|
|
88
|
+
image&.data
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def datas
|
|
92
|
+
images.map(&:data).compact
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def base64?
|
|
96
|
+
image&.base64? || false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def mime_type
|
|
100
|
+
image&.mime_type
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def revised_prompt
|
|
104
|
+
image&.revised_prompt
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Count
|
|
108
|
+
|
|
109
|
+
def count
|
|
110
|
+
images.size
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Timing
|
|
114
|
+
|
|
115
|
+
def duration_ms
|
|
116
|
+
return 0 unless started_at && completed_at
|
|
117
|
+
((completed_at - started_at) * 1000).round
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Cost estimation
|
|
121
|
+
|
|
122
|
+
def total_cost
|
|
123
|
+
return 0 if error?
|
|
124
|
+
|
|
125
|
+
ImageGenerator::Pricing.calculate_cost(
|
|
126
|
+
model_id: model_id,
|
|
127
|
+
size: size,
|
|
128
|
+
count: count
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def input_tokens
|
|
133
|
+
(prompt.length / 4.0).ceil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# File operations
|
|
137
|
+
|
|
138
|
+
def save(path)
|
|
139
|
+
raise "No image to save" unless image
|
|
140
|
+
image.save(path)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def save_all(directory, prefix: "edited")
|
|
144
|
+
images.each_with_index do |img, idx|
|
|
145
|
+
filename = "#{prefix}_#{idx + 1}.png"
|
|
146
|
+
img.save(File.join(directory, filename))
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def to_blob
|
|
151
|
+
image&.to_blob
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def blobs
|
|
155
|
+
images.map(&:to_blob)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Serialization
|
|
159
|
+
|
|
160
|
+
def to_h
|
|
161
|
+
{
|
|
162
|
+
success: success?,
|
|
163
|
+
count: count,
|
|
164
|
+
urls: urls,
|
|
165
|
+
base64: base64?,
|
|
166
|
+
mime_type: mime_type,
|
|
167
|
+
source_image: source_image,
|
|
168
|
+
prompt: prompt,
|
|
169
|
+
model_id: model_id,
|
|
170
|
+
size: size,
|
|
171
|
+
total_cost: total_cost,
|
|
172
|
+
duration_ms: duration_ms,
|
|
173
|
+
started_at: started_at&.iso8601,
|
|
174
|
+
completed_at: completed_at&.iso8601,
|
|
175
|
+
tenant_id: tenant_id,
|
|
176
|
+
editor_class: editor_class,
|
|
177
|
+
error_class: error_class,
|
|
178
|
+
error_message: error_message
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Caching
|
|
183
|
+
|
|
184
|
+
def to_cache
|
|
185
|
+
{
|
|
186
|
+
urls: urls,
|
|
187
|
+
datas: datas,
|
|
188
|
+
mime_type: mime_type,
|
|
189
|
+
model_id: model_id,
|
|
190
|
+
total_cost: total_cost,
|
|
191
|
+
cached_at: Time.current.iso8601
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def self.from_cache(data)
|
|
196
|
+
CachedImageEditResult.new(data)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Lightweight result for cached edits
|
|
201
|
+
class CachedImageEditResult
|
|
202
|
+
attr_reader :urls, :datas, :mime_type, :model_id, :total_cost, :cached_at
|
|
203
|
+
|
|
204
|
+
def initialize(data)
|
|
205
|
+
@urls = data[:urls] || []
|
|
206
|
+
@datas = data[:datas] || []
|
|
207
|
+
@mime_type = data[:mime_type]
|
|
208
|
+
@model_id = data[:model_id]
|
|
209
|
+
@total_cost = data[:total_cost]
|
|
210
|
+
@cached_at = data[:cached_at]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def success?
|
|
214
|
+
urls.any? || datas.any?
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def error?
|
|
218
|
+
!success?
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def cached?
|
|
222
|
+
true
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def url
|
|
226
|
+
urls.first
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def data
|
|
230
|
+
datas.first
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def base64?
|
|
234
|
+
datas.any?
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def count
|
|
238
|
+
[urls.size, datas.size].max
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def single?
|
|
242
|
+
count == 1
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def batch?
|
|
246
|
+
count > 1
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Result wrapper for image generation operations
|
|
6
|
+
#
|
|
7
|
+
# Provides a consistent interface for accessing generated images,
|
|
8
|
+
# metadata, timing, and cost information.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing a single image
|
|
11
|
+
# result = ImageGenerator.call(prompt: "A sunset")
|
|
12
|
+
# result.url # => "https://..."
|
|
13
|
+
# result.success? # => true
|
|
14
|
+
# result.save("sunset.png")
|
|
15
|
+
#
|
|
16
|
+
# @example Accessing multiple images
|
|
17
|
+
# result = ImageGenerator.call(prompt: "Logos", count: 4)
|
|
18
|
+
# result.urls # => ["https://...", ...]
|
|
19
|
+
# result.count # => 4
|
|
20
|
+
# result.save_all("./logos")
|
|
21
|
+
#
|
|
22
|
+
class ImageGenerationResult
|
|
23
|
+
attr_reader :images, :prompt, :model_id, :size, :quality, :style,
|
|
24
|
+
:started_at, :completed_at, :tenant_id, :generator_class,
|
|
25
|
+
:error_class, :error_message
|
|
26
|
+
|
|
27
|
+
# Initialize a new result
|
|
28
|
+
#
|
|
29
|
+
# @param images [Array<Object>] Array of image objects from RubyLLM
|
|
30
|
+
# @param prompt [String] The original prompt
|
|
31
|
+
# @param model_id [String] Model used for generation
|
|
32
|
+
# @param size [String] Image size
|
|
33
|
+
# @param quality [String] Quality setting
|
|
34
|
+
# @param style [String] Style setting
|
|
35
|
+
# @param started_at [Time] When generation started
|
|
36
|
+
# @param completed_at [Time] When generation completed
|
|
37
|
+
# @param tenant_id [String, nil] Tenant identifier
|
|
38
|
+
# @param generator_class [String] Name of the generator class
|
|
39
|
+
# @param error_class [String, nil] Error class name if failed
|
|
40
|
+
# @param error_message [String, nil] Error message if failed
|
|
41
|
+
def initialize(images:, prompt:, model_id:, size:, quality:, style:,
|
|
42
|
+
started_at:, completed_at:, tenant_id:, generator_class:,
|
|
43
|
+
error_class: nil, error_message: nil)
|
|
44
|
+
@images = images
|
|
45
|
+
@prompt = prompt
|
|
46
|
+
@model_id = model_id
|
|
47
|
+
@size = size
|
|
48
|
+
@quality = quality
|
|
49
|
+
@style = style
|
|
50
|
+
@started_at = started_at
|
|
51
|
+
@completed_at = completed_at
|
|
52
|
+
@tenant_id = tenant_id
|
|
53
|
+
@generator_class = generator_class
|
|
54
|
+
@error_class = error_class
|
|
55
|
+
@error_message = error_message
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Status helpers
|
|
59
|
+
|
|
60
|
+
# Check if generation was successful
|
|
61
|
+
#
|
|
62
|
+
# @return [Boolean] true if successful
|
|
63
|
+
def success?
|
|
64
|
+
error_class.nil? && images.any?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if generation failed
|
|
68
|
+
#
|
|
69
|
+
# @return [Boolean] true if failed
|
|
70
|
+
def error?
|
|
71
|
+
!success?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if this was a single image request
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean] true if single image
|
|
77
|
+
def single?
|
|
78
|
+
count == 1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check if this was a batch request
|
|
82
|
+
#
|
|
83
|
+
# @return [Boolean] true if multiple images
|
|
84
|
+
def batch?
|
|
85
|
+
count > 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Image access
|
|
89
|
+
|
|
90
|
+
# Get the first/only image
|
|
91
|
+
#
|
|
92
|
+
# @return [Object, nil] The first image object
|
|
93
|
+
def image
|
|
94
|
+
images.first
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get the URL of the first image
|
|
98
|
+
#
|
|
99
|
+
# @return [String, nil] The image URL
|
|
100
|
+
def url
|
|
101
|
+
image&.url
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Get all image URLs
|
|
105
|
+
#
|
|
106
|
+
# @return [Array<String>] Array of URLs
|
|
107
|
+
def urls
|
|
108
|
+
images.map(&:url).compact
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get the base64 data of the first image
|
|
112
|
+
#
|
|
113
|
+
# @return [String, nil] Base64 encoded image data
|
|
114
|
+
def data
|
|
115
|
+
image&.data
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Get all base64 data
|
|
119
|
+
#
|
|
120
|
+
# @return [Array<String>] Array of base64 data
|
|
121
|
+
def datas
|
|
122
|
+
images.map(&:data).compact
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check if the image is base64 encoded
|
|
126
|
+
#
|
|
127
|
+
# @return [Boolean] true if base64
|
|
128
|
+
def base64?
|
|
129
|
+
image&.base64? || false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get the MIME type
|
|
133
|
+
#
|
|
134
|
+
# @return [String, nil] MIME type
|
|
135
|
+
def mime_type
|
|
136
|
+
image&.mime_type
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get the revised prompt (if model modified it)
|
|
140
|
+
#
|
|
141
|
+
# @return [String, nil] The revised prompt
|
|
142
|
+
def revised_prompt
|
|
143
|
+
image&.revised_prompt
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Get all revised prompts
|
|
147
|
+
#
|
|
148
|
+
# @return [Array<String>] Array of revised prompts
|
|
149
|
+
def revised_prompts
|
|
150
|
+
images.map(&:revised_prompt).compact
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Count
|
|
154
|
+
|
|
155
|
+
# Get the number of generated images
|
|
156
|
+
#
|
|
157
|
+
# @return [Integer] Image count
|
|
158
|
+
def count
|
|
159
|
+
images.size
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Timing
|
|
163
|
+
|
|
164
|
+
# Get the generation duration in milliseconds
|
|
165
|
+
#
|
|
166
|
+
# @return [Integer] Duration in ms
|
|
167
|
+
def duration_ms
|
|
168
|
+
return 0 unless started_at && completed_at
|
|
169
|
+
((completed_at - started_at) * 1000).round
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Cost estimation
|
|
173
|
+
|
|
174
|
+
# Get the total cost for this generation
|
|
175
|
+
#
|
|
176
|
+
# Uses dynamic pricing from the Pricing module
|
|
177
|
+
#
|
|
178
|
+
# @return [Float] Total cost in USD
|
|
179
|
+
def total_cost
|
|
180
|
+
return 0 if error?
|
|
181
|
+
|
|
182
|
+
ImageGenerator::Pricing.calculate_cost(
|
|
183
|
+
model_id: model_id,
|
|
184
|
+
size: size,
|
|
185
|
+
quality: quality,
|
|
186
|
+
count: count
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Estimate input tokens from prompt
|
|
191
|
+
#
|
|
192
|
+
# @return [Integer] Approximate token count
|
|
193
|
+
def input_tokens
|
|
194
|
+
# Approximate token count for prompt
|
|
195
|
+
(prompt.length / 4.0).ceil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# File operations
|
|
199
|
+
|
|
200
|
+
# Save the first image to a file
|
|
201
|
+
#
|
|
202
|
+
# @param path [String] File path to save to
|
|
203
|
+
# @raise [RuntimeError] If no image to save
|
|
204
|
+
def save(path)
|
|
205
|
+
raise "No image to save" unless image
|
|
206
|
+
image.save(path)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Save all images to a directory
|
|
210
|
+
#
|
|
211
|
+
# @param directory [String] Directory path
|
|
212
|
+
# @param prefix [String] Filename prefix
|
|
213
|
+
def save_all(directory, prefix: "image")
|
|
214
|
+
images.each_with_index do |img, idx|
|
|
215
|
+
filename = "#{prefix}_#{idx + 1}.png"
|
|
216
|
+
img.save(File.join(directory, filename))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Get the first image as binary data
|
|
221
|
+
#
|
|
222
|
+
# @return [String, nil] Binary image data
|
|
223
|
+
def to_blob
|
|
224
|
+
image&.to_blob
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Get all images as binary data
|
|
228
|
+
#
|
|
229
|
+
# @return [Array<String>] Array of binary data
|
|
230
|
+
def blobs
|
|
231
|
+
images.map(&:to_blob)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Serialization
|
|
235
|
+
|
|
236
|
+
# Convert to hash
|
|
237
|
+
#
|
|
238
|
+
# @return [Hash] Hash representation
|
|
239
|
+
def to_h
|
|
240
|
+
{
|
|
241
|
+
success: success?,
|
|
242
|
+
count: count,
|
|
243
|
+
urls: urls,
|
|
244
|
+
base64: base64?,
|
|
245
|
+
mime_type: mime_type,
|
|
246
|
+
prompt: prompt,
|
|
247
|
+
revised_prompts: revised_prompts,
|
|
248
|
+
model_id: model_id,
|
|
249
|
+
size: size,
|
|
250
|
+
quality: quality,
|
|
251
|
+
style: style,
|
|
252
|
+
total_cost: total_cost,
|
|
253
|
+
input_tokens: input_tokens,
|
|
254
|
+
duration_ms: duration_ms,
|
|
255
|
+
started_at: started_at&.iso8601,
|
|
256
|
+
completed_at: completed_at&.iso8601,
|
|
257
|
+
tenant_id: tenant_id,
|
|
258
|
+
generator_class: generator_class,
|
|
259
|
+
error_class: error_class,
|
|
260
|
+
error_message: error_message
|
|
261
|
+
}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Caching
|
|
265
|
+
|
|
266
|
+
# Convert to cacheable format
|
|
267
|
+
#
|
|
268
|
+
# @return [Hash] Cache-friendly hash
|
|
269
|
+
def to_cache
|
|
270
|
+
{
|
|
271
|
+
urls: urls,
|
|
272
|
+
datas: datas,
|
|
273
|
+
mime_type: mime_type,
|
|
274
|
+
revised_prompts: revised_prompts,
|
|
275
|
+
model_id: model_id,
|
|
276
|
+
total_cost: total_cost,
|
|
277
|
+
cached_at: Time.current.iso8601
|
|
278
|
+
}
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Create a result from cached data
|
|
282
|
+
#
|
|
283
|
+
# @param data [Hash] Cached data
|
|
284
|
+
# @return [CachedImageGenerationResult] The cached result
|
|
285
|
+
def self.from_cache(data)
|
|
286
|
+
CachedImageGenerationResult.new(data)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Lightweight result for cached images
|
|
291
|
+
#
|
|
292
|
+
# Provides a subset of ImageGenerationResult functionality
|
|
293
|
+
# for results loaded from cache.
|
|
294
|
+
#
|
|
295
|
+
class CachedImageGenerationResult
|
|
296
|
+
attr_reader :urls, :datas, :mime_type, :revised_prompts, :model_id,
|
|
297
|
+
:total_cost, :cached_at
|
|
298
|
+
|
|
299
|
+
def initialize(data)
|
|
300
|
+
@urls = data[:urls] || []
|
|
301
|
+
@datas = data[:datas] || []
|
|
302
|
+
@mime_type = data[:mime_type]
|
|
303
|
+
@revised_prompts = data[:revised_prompts] || []
|
|
304
|
+
@model_id = data[:model_id]
|
|
305
|
+
@total_cost = data[:total_cost]
|
|
306
|
+
@cached_at = data[:cached_at]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def success?
|
|
310
|
+
urls.any? || datas.any?
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def error?
|
|
314
|
+
!success?
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def cached?
|
|
318
|
+
true
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def url
|
|
322
|
+
urls.first
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def data
|
|
326
|
+
datas.first
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def base64?
|
|
330
|
+
datas.any?
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def count
|
|
334
|
+
[urls.size, datas.size].max
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def single?
|
|
338
|
+
count == 1
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def batch?
|
|
342
|
+
count > 1
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|