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,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Result wrapper for image upscaling operations
|
|
6
|
+
#
|
|
7
|
+
# Provides a consistent interface for accessing upscaled images,
|
|
8
|
+
# metadata, timing, and cost information.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing upscaled image
|
|
11
|
+
# result = ImageUpscaler.call(image: "low_res.jpg", scale: 4)
|
|
12
|
+
# result.url # => "https://..."
|
|
13
|
+
# result.scale # => 4
|
|
14
|
+
# result.output_size # => "4096x4096"
|
|
15
|
+
# result.success? # => true
|
|
16
|
+
#
|
|
17
|
+
class ImageUpscaleResult
|
|
18
|
+
attr_reader :image, :source_image, :model_id, :scale, :output_size, :face_enhance,
|
|
19
|
+
:started_at, :completed_at, :tenant_id, :upscaler_class,
|
|
20
|
+
:error_class, :error_message
|
|
21
|
+
|
|
22
|
+
# Initialize a new result
|
|
23
|
+
#
|
|
24
|
+
# @param image [Object] The upscaled image object
|
|
25
|
+
# @param source_image [String] The original source image
|
|
26
|
+
# @param model_id [String] Model used for upscaling
|
|
27
|
+
# @param scale [Integer] Upscale factor used
|
|
28
|
+
# @param output_size [String] Output image dimensions
|
|
29
|
+
# @param face_enhance [Boolean] Whether face enhancement was used
|
|
30
|
+
# @param started_at [Time] When upscaling started
|
|
31
|
+
# @param completed_at [Time] When upscaling completed
|
|
32
|
+
# @param tenant_id [String, nil] Tenant identifier
|
|
33
|
+
# @param upscaler_class [String] Name of the upscaler class
|
|
34
|
+
# @param error_class [String, nil] Error class name if failed
|
|
35
|
+
# @param error_message [String, nil] Error message if failed
|
|
36
|
+
def initialize(image:, source_image:, model_id:, scale:, output_size:, face_enhance:,
|
|
37
|
+
started_at:, completed_at:, tenant_id:, upscaler_class:,
|
|
38
|
+
error_class: nil, error_message: nil)
|
|
39
|
+
@image = image
|
|
40
|
+
@source_image = source_image
|
|
41
|
+
@model_id = model_id
|
|
42
|
+
@scale = scale
|
|
43
|
+
@output_size = output_size
|
|
44
|
+
@face_enhance = face_enhance
|
|
45
|
+
@started_at = started_at
|
|
46
|
+
@completed_at = completed_at
|
|
47
|
+
@tenant_id = tenant_id
|
|
48
|
+
@upscaler_class = upscaler_class
|
|
49
|
+
@error_class = error_class
|
|
50
|
+
@error_message = error_message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Status helpers
|
|
54
|
+
|
|
55
|
+
def success?
|
|
56
|
+
error_class.nil? && !image.nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def error?
|
|
60
|
+
!success?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Always single image for upscaling
|
|
64
|
+
def single?
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def batch?
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Image access
|
|
73
|
+
|
|
74
|
+
def url
|
|
75
|
+
image&.url
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def urls
|
|
79
|
+
success? ? [url].compact : []
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def data
|
|
83
|
+
image&.data
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def datas
|
|
87
|
+
success? ? [data].compact : []
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def base64?
|
|
91
|
+
image&.base64? || false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def mime_type
|
|
95
|
+
image&.mime_type
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Count (always 1 for upscaling)
|
|
99
|
+
|
|
100
|
+
def count
|
|
101
|
+
success? ? 1 : 0
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Size helpers
|
|
105
|
+
|
|
106
|
+
def size
|
|
107
|
+
output_size
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def output_width
|
|
111
|
+
return nil unless output_size
|
|
112
|
+
output_size.split("x").first.to_i
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def output_height
|
|
116
|
+
return nil unless output_size
|
|
117
|
+
output_size.split("x").last.to_i
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Timing
|
|
121
|
+
|
|
122
|
+
def duration_ms
|
|
123
|
+
return 0 unless started_at && completed_at
|
|
124
|
+
((completed_at - started_at) * 1000).round
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Cost estimation
|
|
128
|
+
|
|
129
|
+
def total_cost
|
|
130
|
+
return 0 if error?
|
|
131
|
+
|
|
132
|
+
# Upscaling typically has fixed per-image cost
|
|
133
|
+
ImageGenerator::Pricing.calculate_cost(
|
|
134
|
+
model_id: model_id,
|
|
135
|
+
count: 1
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# File operations
|
|
140
|
+
|
|
141
|
+
def save(path)
|
|
142
|
+
raise "No image to save" unless image
|
|
143
|
+
image.save(path)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def to_blob
|
|
147
|
+
image&.to_blob
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def blobs
|
|
151
|
+
success? ? [to_blob].compact : []
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Serialization
|
|
155
|
+
|
|
156
|
+
def to_h
|
|
157
|
+
{
|
|
158
|
+
success: success?,
|
|
159
|
+
url: url,
|
|
160
|
+
base64: base64?,
|
|
161
|
+
mime_type: mime_type,
|
|
162
|
+
source_image: source_image,
|
|
163
|
+
model_id: model_id,
|
|
164
|
+
scale: scale,
|
|
165
|
+
output_size: output_size,
|
|
166
|
+
face_enhance: face_enhance,
|
|
167
|
+
total_cost: total_cost,
|
|
168
|
+
duration_ms: duration_ms,
|
|
169
|
+
started_at: started_at&.iso8601,
|
|
170
|
+
completed_at: completed_at&.iso8601,
|
|
171
|
+
tenant_id: tenant_id,
|
|
172
|
+
upscaler_class: upscaler_class,
|
|
173
|
+
error_class: error_class,
|
|
174
|
+
error_message: error_message
|
|
175
|
+
}
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Caching
|
|
179
|
+
|
|
180
|
+
def to_cache
|
|
181
|
+
{
|
|
182
|
+
url: url,
|
|
183
|
+
data: data,
|
|
184
|
+
mime_type: mime_type,
|
|
185
|
+
model_id: model_id,
|
|
186
|
+
scale: scale,
|
|
187
|
+
output_size: output_size,
|
|
188
|
+
total_cost: total_cost,
|
|
189
|
+
cached_at: Time.current.iso8601
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def self.from_cache(data)
|
|
194
|
+
CachedImageUpscaleResult.new(data)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Lightweight result for cached upscales
|
|
199
|
+
class CachedImageUpscaleResult
|
|
200
|
+
attr_reader :url, :data, :mime_type, :model_id, :scale, :output_size,
|
|
201
|
+
:total_cost, :cached_at
|
|
202
|
+
|
|
203
|
+
def initialize(data)
|
|
204
|
+
@url = data[:url]
|
|
205
|
+
@data = data[:data]
|
|
206
|
+
@mime_type = data[:mime_type]
|
|
207
|
+
@model_id = data[:model_id]
|
|
208
|
+
@scale = data[:scale]
|
|
209
|
+
@output_size = data[:output_size]
|
|
210
|
+
@total_cost = data[:total_cost]
|
|
211
|
+
@cached_at = data[:cached_at]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def success?
|
|
215
|
+
!url.nil? || !data.nil?
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def error?
|
|
219
|
+
!success?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def cached?
|
|
223
|
+
true
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def urls
|
|
227
|
+
success? ? [url].compact : []
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def datas
|
|
231
|
+
success? ? [data].compact : []
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def base64?
|
|
235
|
+
!data.nil?
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def count
|
|
239
|
+
success? ? 1 : 0
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def single?
|
|
243
|
+
true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def batch?
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def size
|
|
251
|
+
output_size
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Result wrapper for image variation operations
|
|
6
|
+
#
|
|
7
|
+
# Provides a consistent interface for accessing variation images,
|
|
8
|
+
# metadata, timing, and cost information.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing variations
|
|
11
|
+
# result = ImageVariator.call(image: "logo.png", count: 4)
|
|
12
|
+
# result.urls # => ["https://...", ...]
|
|
13
|
+
# result.count # => 4
|
|
14
|
+
# result.success? # => true
|
|
15
|
+
#
|
|
16
|
+
class ImageVariationResult
|
|
17
|
+
attr_reader :images, :source_image, :model_id, :size, :variation_strength,
|
|
18
|
+
:started_at, :completed_at, :tenant_id, :variator_class,
|
|
19
|
+
:error_class, :error_message
|
|
20
|
+
|
|
21
|
+
# Initialize a new result
|
|
22
|
+
#
|
|
23
|
+
# @param images [Array<Object>] Array of variation image objects
|
|
24
|
+
# @param source_image [String] The original source image
|
|
25
|
+
# @param model_id [String] Model used for variation
|
|
26
|
+
# @param size [String] Image size
|
|
27
|
+
# @param variation_strength [Float] Variation strength used
|
|
28
|
+
# @param started_at [Time] When variation started
|
|
29
|
+
# @param completed_at [Time] When variation completed
|
|
30
|
+
# @param tenant_id [String, nil] Tenant identifier
|
|
31
|
+
# @param variator_class [String] Name of the variator class
|
|
32
|
+
# @param error_class [String, nil] Error class name if failed
|
|
33
|
+
# @param error_message [String, nil] Error message if failed
|
|
34
|
+
def initialize(images:, source_image:, model_id:, size:, variation_strength:,
|
|
35
|
+
started_at:, completed_at:, tenant_id:, variator_class:,
|
|
36
|
+
error_class: nil, error_message: nil)
|
|
37
|
+
@images = images
|
|
38
|
+
@source_image = source_image
|
|
39
|
+
@model_id = model_id
|
|
40
|
+
@size = size
|
|
41
|
+
@variation_strength = variation_strength
|
|
42
|
+
@started_at = started_at
|
|
43
|
+
@completed_at = completed_at
|
|
44
|
+
@tenant_id = tenant_id
|
|
45
|
+
@variator_class = variator_class
|
|
46
|
+
@error_class = error_class
|
|
47
|
+
@error_message = error_message
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Status helpers
|
|
51
|
+
|
|
52
|
+
def success?
|
|
53
|
+
error_class.nil? && images.any?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def error?
|
|
57
|
+
!success?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def single?
|
|
61
|
+
count == 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def batch?
|
|
65
|
+
count > 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Image access
|
|
69
|
+
|
|
70
|
+
def image
|
|
71
|
+
images.first
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def url
|
|
75
|
+
image&.url
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def urls
|
|
79
|
+
images.map(&:url).compact
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def data
|
|
83
|
+
image&.data
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def datas
|
|
87
|
+
images.map(&:data).compact
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def base64?
|
|
91
|
+
image&.base64? || false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def mime_type
|
|
95
|
+
image&.mime_type
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Count
|
|
99
|
+
|
|
100
|
+
def count
|
|
101
|
+
images.size
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Timing
|
|
105
|
+
|
|
106
|
+
def duration_ms
|
|
107
|
+
return 0 unless started_at && completed_at
|
|
108
|
+
((completed_at - started_at) * 1000).round
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Cost estimation
|
|
112
|
+
|
|
113
|
+
def total_cost
|
|
114
|
+
return 0 if error?
|
|
115
|
+
|
|
116
|
+
ImageGenerator::Pricing.calculate_cost(
|
|
117
|
+
model_id: model_id,
|
|
118
|
+
size: size,
|
|
119
|
+
count: count
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# File operations
|
|
124
|
+
|
|
125
|
+
def save(path)
|
|
126
|
+
raise "No image to save" unless image
|
|
127
|
+
image.save(path)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def save_all(directory, prefix: "variation")
|
|
131
|
+
images.each_with_index do |img, idx|
|
|
132
|
+
filename = "#{prefix}_#{idx + 1}.png"
|
|
133
|
+
img.save(File.join(directory, filename))
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def to_blob
|
|
138
|
+
image&.to_blob
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def blobs
|
|
142
|
+
images.map(&:to_blob)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Serialization
|
|
146
|
+
|
|
147
|
+
def to_h
|
|
148
|
+
{
|
|
149
|
+
success: success?,
|
|
150
|
+
count: count,
|
|
151
|
+
urls: urls,
|
|
152
|
+
base64: base64?,
|
|
153
|
+
mime_type: mime_type,
|
|
154
|
+
source_image: source_image,
|
|
155
|
+
model_id: model_id,
|
|
156
|
+
size: size,
|
|
157
|
+
variation_strength: variation_strength,
|
|
158
|
+
total_cost: total_cost,
|
|
159
|
+
duration_ms: duration_ms,
|
|
160
|
+
started_at: started_at&.iso8601,
|
|
161
|
+
completed_at: completed_at&.iso8601,
|
|
162
|
+
tenant_id: tenant_id,
|
|
163
|
+
variator_class: variator_class,
|
|
164
|
+
error_class: error_class,
|
|
165
|
+
error_message: error_message
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Caching
|
|
170
|
+
|
|
171
|
+
def to_cache
|
|
172
|
+
{
|
|
173
|
+
urls: urls,
|
|
174
|
+
datas: datas,
|
|
175
|
+
mime_type: mime_type,
|
|
176
|
+
model_id: model_id,
|
|
177
|
+
total_cost: total_cost,
|
|
178
|
+
cached_at: Time.current.iso8601
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.from_cache(data)
|
|
183
|
+
CachedImageVariationResult.new(data)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Lightweight result for cached variations
|
|
188
|
+
class CachedImageVariationResult
|
|
189
|
+
attr_reader :urls, :datas, :mime_type, :model_id, :total_cost, :cached_at
|
|
190
|
+
|
|
191
|
+
def initialize(data)
|
|
192
|
+
@urls = data[:urls] || []
|
|
193
|
+
@datas = data[:datas] || []
|
|
194
|
+
@mime_type = data[:mime_type]
|
|
195
|
+
@model_id = data[:model_id]
|
|
196
|
+
@total_cost = data[:total_cost]
|
|
197
|
+
@cached_at = data[:cached_at]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def success?
|
|
201
|
+
urls.any? || datas.any?
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def error?
|
|
205
|
+
!success?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def cached?
|
|
209
|
+
true
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def url
|
|
213
|
+
urls.first
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def data
|
|
217
|
+
datas.first
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def base64?
|
|
221
|
+
datas.any?
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def count
|
|
225
|
+
[urls.size, datas.size].max
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def single?
|
|
229
|
+
count == 1
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def batch?
|
|
233
|
+
count > 1
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Wrapper for moderation results with threshold and category filtering
|
|
6
|
+
#
|
|
7
|
+
# Provides a filtered view of moderation results based on configured
|
|
8
|
+
# thresholds and category filters. The raw result is still accessible
|
|
9
|
+
# for full details.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# result = ModerationResult.new(
|
|
13
|
+
# result: raw_moderation,
|
|
14
|
+
# threshold: 0.8,
|
|
15
|
+
# categories: [:hate, :violence]
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# result.flagged? # Only true if score >= 0.8 AND category is hate or violence
|
|
19
|
+
# result.passed? # Opposite of flagged?
|
|
20
|
+
#
|
|
21
|
+
# @api public
|
|
22
|
+
class ModerationResult
|
|
23
|
+
# @return [Object] The raw moderation result from RubyLLM
|
|
24
|
+
attr_reader :raw_result
|
|
25
|
+
|
|
26
|
+
# @return [Float, nil] Configured threshold for flagging
|
|
27
|
+
attr_reader :threshold
|
|
28
|
+
|
|
29
|
+
# @return [Array<Symbol>, nil] Categories to filter on
|
|
30
|
+
attr_reader :filter_categories
|
|
31
|
+
|
|
32
|
+
# Creates a new ModerationResult
|
|
33
|
+
#
|
|
34
|
+
# @param result [Object] Raw moderation result from RubyLLM
|
|
35
|
+
# @param threshold [Float, nil] Score threshold (0.0-1.0)
|
|
36
|
+
# @param categories [Array<Symbol>, nil] Categories to check
|
|
37
|
+
def initialize(result:, threshold: nil, categories: nil)
|
|
38
|
+
@raw_result = result
|
|
39
|
+
@threshold = threshold
|
|
40
|
+
@filter_categories = categories&.map(&:to_sym)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns whether the content should be flagged
|
|
44
|
+
#
|
|
45
|
+
# Considers both threshold and category filters if configured.
|
|
46
|
+
# Content is flagged only if:
|
|
47
|
+
# - Raw result is flagged AND
|
|
48
|
+
# - Score meets threshold (if configured) AND
|
|
49
|
+
# - Category matches filter (if configured)
|
|
50
|
+
#
|
|
51
|
+
# @return [Boolean] true if content should be flagged
|
|
52
|
+
def flagged?
|
|
53
|
+
return false unless raw_result.flagged?
|
|
54
|
+
|
|
55
|
+
passes_threshold? && passes_category_filter?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns whether the content passed moderation
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean] true if content is not flagged
|
|
61
|
+
def passed?
|
|
62
|
+
!flagged?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the flagged categories, filtered by configuration
|
|
66
|
+
#
|
|
67
|
+
# @return [Array<String, Symbol>] Categories that triggered flagging
|
|
68
|
+
def flagged_categories
|
|
69
|
+
cats = raw_result.flagged_categories || []
|
|
70
|
+
return cats unless filter_categories&.any?
|
|
71
|
+
|
|
72
|
+
cats.select { |c| filter_categories.include?(normalize_category(c)) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns all category scores from the raw result
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash{String, Symbol => Float}] Category to score mapping
|
|
78
|
+
def category_scores
|
|
79
|
+
raw_result.category_scores || {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the moderation result ID
|
|
83
|
+
#
|
|
84
|
+
# @return [String, nil] Result identifier
|
|
85
|
+
def id
|
|
86
|
+
raw_result.id
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the model used for moderation
|
|
90
|
+
#
|
|
91
|
+
# @return [String, nil] Model identifier
|
|
92
|
+
def model
|
|
93
|
+
raw_result.model
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the maximum score across all categories
|
|
97
|
+
#
|
|
98
|
+
# @return [Float] Highest category score
|
|
99
|
+
def max_score
|
|
100
|
+
scores = category_scores.values
|
|
101
|
+
scores.any? ? scores.max : 0.0
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns whether the raw result was flagged (ignoring filters)
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] true if raw result was flagged
|
|
107
|
+
def raw_flagged?
|
|
108
|
+
raw_result.flagged?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Converts the result to a hash
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash] Result data as a hash
|
|
114
|
+
def to_h
|
|
115
|
+
{
|
|
116
|
+
flagged: flagged?,
|
|
117
|
+
raw_flagged: raw_flagged?,
|
|
118
|
+
flagged_categories: flagged_categories,
|
|
119
|
+
category_scores: category_scores,
|
|
120
|
+
max_score: max_score,
|
|
121
|
+
threshold: threshold,
|
|
122
|
+
filter_categories: filter_categories,
|
|
123
|
+
model: model,
|
|
124
|
+
id: id
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# Checks if the max score meets the threshold
|
|
131
|
+
#
|
|
132
|
+
# @return [Boolean] true if threshold is met or not configured
|
|
133
|
+
def passes_threshold?
|
|
134
|
+
return true unless threshold
|
|
135
|
+
|
|
136
|
+
max_score >= threshold
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Checks if any flagged categories match the filter
|
|
140
|
+
#
|
|
141
|
+
# @return [Boolean] true if categories match or no filter configured
|
|
142
|
+
def passes_category_filter?
|
|
143
|
+
return true unless filter_categories&.any?
|
|
144
|
+
|
|
145
|
+
normalized_flagged = (raw_result.flagged_categories || []).map { |c| normalize_category(c) }
|
|
146
|
+
(normalized_flagged & filter_categories).any?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Normalizes category names for comparison
|
|
150
|
+
#
|
|
151
|
+
# @param category [String, Symbol] Category name
|
|
152
|
+
# @return [Symbol] Normalized category symbol
|
|
153
|
+
def normalize_category(category)
|
|
154
|
+
category.to_s.tr("/", "_").tr("-", "_").downcase.to_sym
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|