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.
Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +189 -31
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  5. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  6. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  7. data/app/models/ruby_llm/agents/execution.rb +3 -0
  8. data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
  9. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  10. data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
  11. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  12. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  13. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  14. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  15. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  16. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  17. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  18. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  19. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  20. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  21. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  22. data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
  23. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  24. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  25. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  26. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  27. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  28. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  29. data/config/routes.rb +1 -0
  30. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  31. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  32. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  33. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  34. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  35. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  36. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  37. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  38. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  39. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  40. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  41. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  42. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  43. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  44. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  45. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  46. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  47. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  48. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  49. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  50. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  51. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  52. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  53. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  54. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  55. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  56. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  57. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  58. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  60. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  61. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  62. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  63. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  64. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  65. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  66. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  67. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  68. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  69. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  70. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  71. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  72. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  73. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  74. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  75. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  76. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  77. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  78. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  79. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  80. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  81. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  82. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  83. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  84. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  85. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  86. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  87. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  88. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  89. data/lib/ruby_llm/agents/core/base.rb +135 -0
  90. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  91. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  92. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
  93. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  94. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  95. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  96. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  97. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  98. data/lib/ruby_llm/agents/dsl.rb +41 -0
  99. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  100. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  101. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  102. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  103. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  104. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  105. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  106. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  107. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  108. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  109. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  110. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  111. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  112. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  113. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  114. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  115. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  116. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  117. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  118. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  119. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  120. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  121. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  122. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  123. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  124. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  125. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  126. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  127. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  128. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  129. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  130. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  131. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  132. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  133. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  134. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  135. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  136. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  137. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  138. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  139. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  140. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  141. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  142. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  143. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  144. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  145. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  146. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  147. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
  148. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  149. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  150. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  151. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  152. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  153. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  154. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  155. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  156. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  157. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  158. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  159. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  160. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  161. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  162. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  163. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  164. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  165. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  166. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  167. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  168. data/lib/ruby_llm/agents.rb +86 -20
  169. metadata +172 -34
  170. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  171. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  172. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  173. data/lib/ruby_llm/agents/base/execution.rb +0 -366
  174. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  175. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  176. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  177. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  178. data/lib/ruby_llm/agents/base.rb +0 -210
  179. data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
  180. data/lib/ruby_llm/agents/configuration.rb +0 -394
  181. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  182. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  183. /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
  184. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  185. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  186. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  187. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  188. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  189. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  190. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # Embedder generator for creating new embedders
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:embedder Document
10
+ # rails generate ruby_llm_agents:embedder Document --model text-embedding-3-large
11
+ # rails generate ruby_llm_agents:embedder Document --dimensions 512
12
+ # rails generate ruby_llm_agents:embedder Document --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/text/embedders/document_embedder.rb
16
+ #
17
+ class EmbedderGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :model, type: :string, default: "text-embedding-3-small",
21
+ desc: "The embedding model to use"
22
+ class_option :dimensions, type: :numeric, default: nil,
23
+ desc: "Vector dimensions (nil for model default)"
24
+ class_option :batch_size, type: :numeric, default: 100,
25
+ desc: "Texts per API call for batch processing"
26
+ class_option :cache, type: :string, default: nil,
27
+ desc: "Cache TTL (e.g., '1.week', '1.day')"
28
+ class_option :root,
29
+ type: :string,
30
+ default: nil,
31
+ desc: "Root directory name (default: uses config or 'llm')"
32
+ class_option :namespace,
33
+ type: :string,
34
+ default: nil,
35
+ desc: "Root namespace (default: camelized root or config)"
36
+
37
+ def ensure_base_class_and_skill_file
38
+ @root_namespace = root_namespace
39
+ @text_namespace = "#{root_namespace}::Text"
40
+ embedders_dir = "app/#{root_directory}/text/embedders"
41
+
42
+ # Create directory if needed
43
+ empty_directory embedders_dir
44
+
45
+ # Create base class if it doesn't exist
46
+ base_class_path = "#{embedders_dir}/application_embedder.rb"
47
+ unless File.exist?(File.join(destination_root, base_class_path))
48
+ template "application_embedder.rb.tt", base_class_path
49
+ end
50
+
51
+ # Create skill file if it doesn't exist
52
+ skill_file_path = "#{embedders_dir}/EMBEDDERS.md"
53
+ unless File.exist?(File.join(destination_root, skill_file_path))
54
+ template "skills/EMBEDDERS.md.tt", skill_file_path
55
+ end
56
+ end
57
+
58
+ def create_embedder_file
59
+ # Support nested paths: "search/document" -> "app/{root}/text/embedders/search/document_embedder.rb"
60
+ @root_namespace = root_namespace
61
+ @text_namespace = "#{root_namespace}::Text"
62
+ embedder_path = name.underscore
63
+ template "embedder.rb.tt", "app/#{root_directory}/text/embedders/#{embedder_path}_embedder.rb"
64
+ end
65
+
66
+ def show_usage
67
+ # Build full class name from path
68
+ embedder_class_name = name.split("/").map(&:camelize).join("::")
69
+ full_class_name = "#{root_namespace}::Text::#{embedder_class_name}Embedder"
70
+ say ""
71
+ say "Embedder #{full_class_name} created!", :green
72
+ say ""
73
+ say "Usage:"
74
+ say " # Single text"
75
+ say " #{full_class_name}.call(text: \"Hello world\")"
76
+ say ""
77
+ say " # Multiple texts (batch)"
78
+ say " #{full_class_name}.call(texts: [\"Hello\", \"World\"])"
79
+ say ""
80
+ say " # With progress tracking"
81
+ say " #{full_class_name}.call(texts: large_array) do |batch, idx|"
82
+ say " puts \"Processed batch \#{idx}\""
83
+ say " end"
84
+ say ""
85
+ end
86
+
87
+ private
88
+
89
+ def root_directory
90
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
91
+ end
92
+
93
+ def root_namespace
94
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
95
+ end
96
+
97
+ def camelize(str)
98
+ # Handle special cases for common abbreviations
99
+ return "AI" if str.downcase == "ai"
100
+ return "ML" if str.downcase == "ml"
101
+ return "LLM" if str.downcase == "llm"
102
+
103
+ # Standard camelization
104
+ str.split(/[-_]/).map(&:capitalize).join
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # ImageAnalyzer generator for creating new image analyzers
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:image_analyzer Product
10
+ # rails generate ruby_llm_agents:image_analyzer Content --model gpt-4o --analysis_type detailed
11
+ # rails generate ruby_llm_agents:image_analyzer Photo --extract_colors --detect_objects
12
+ # rails generate ruby_llm_agents:image_analyzer Product --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/image/analyzers/product_analyzer.rb
16
+ #
17
+ class ImageAnalyzerGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :model, type: :string, default: "gpt-4o",
21
+ desc: "The vision model to use"
22
+ class_option :analysis_type, type: :string, default: "detailed",
23
+ desc: "Analysis type (caption, detailed, tags, objects, colors, all)"
24
+ class_option :extract_colors, type: :boolean, default: false,
25
+ desc: "Enable color extraction"
26
+ class_option :detect_objects, type: :boolean, default: false,
27
+ desc: "Enable object detection"
28
+ class_option :extract_text, type: :boolean, default: false,
29
+ desc: "Enable text extraction (OCR)"
30
+ class_option :max_tags, type: :string, default: "10",
31
+ desc: "Maximum number of tags to return"
32
+ class_option :cache, type: :string, default: nil,
33
+ desc: "Cache TTL (e.g., '1.hour', '1.day')"
34
+ class_option :root,
35
+ type: :string,
36
+ default: nil,
37
+ desc: "Root directory name (default: uses config or 'llm')"
38
+ class_option :namespace,
39
+ type: :string,
40
+ default: nil,
41
+ desc: "Root namespace (default: camelized root or config)"
42
+
43
+ def ensure_base_class_and_skill_file
44
+ @root_namespace = root_namespace
45
+ @image_namespace = "#{root_namespace}::Image"
46
+ analyzers_dir = "app/#{root_directory}/image/analyzers"
47
+
48
+ # Create directory if needed
49
+ empty_directory analyzers_dir
50
+
51
+ # Create base class if it doesn't exist
52
+ base_class_path = "#{analyzers_dir}/application_image_analyzer.rb"
53
+ unless File.exist?(File.join(destination_root, base_class_path))
54
+ template "application_image_analyzer.rb.tt", base_class_path
55
+ end
56
+
57
+ # Create skill file if it doesn't exist
58
+ skill_file_path = "#{analyzers_dir}/IMAGE_ANALYZERS.md"
59
+ unless File.exist?(File.join(destination_root, skill_file_path))
60
+ template "skills/IMAGE_ANALYZERS.md.tt", skill_file_path
61
+ end
62
+ end
63
+
64
+ def create_image_analyzer_file
65
+ @root_namespace = root_namespace
66
+ @image_namespace = "#{root_namespace}::Image"
67
+ analyzer_path = name.underscore
68
+ template "image_analyzer.rb.tt", "app/#{root_directory}/image/analyzers/#{analyzer_path}_analyzer.rb"
69
+ end
70
+
71
+ def show_usage
72
+ analyzer_class_name = name.split("/").map(&:camelize).join("::")
73
+ full_class_name = "#{root_namespace}::Image::#{analyzer_class_name}Analyzer"
74
+ say ""
75
+ say "Image analyzer #{full_class_name} created!", :green
76
+ say ""
77
+ say "Usage:"
78
+ say " # Analyze an image"
79
+ say " result = #{full_class_name}.call(image: 'photo.jpg')"
80
+ say " result.caption # => 'A sunset over mountains'"
81
+ say " result.tags # => ['sunset', 'mountains', 'nature']"
82
+ say " result.description # => 'Detailed description...'"
83
+ say ""
84
+ say " # Override settings at runtime"
85
+ say " result = #{full_class_name}.call("
86
+ say " image: 'product.jpg',"
87
+ say " analysis_type: :all,"
88
+ say " extract_colors: true,"
89
+ say " detect_objects: true"
90
+ say " )"
91
+ say ""
92
+ say " # Access detected objects and colors"
93
+ say " result.objects # => [{name: 'laptop', location: 'center', confidence: 'high'}]"
94
+ say " result.colors # => [{hex: '#C0C0C0', name: 'silver', percentage: 45}]"
95
+ say ""
96
+ end
97
+
98
+ private
99
+
100
+ def root_directory
101
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
102
+ end
103
+
104
+ def root_namespace
105
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
106
+ end
107
+
108
+ def camelize(str)
109
+ return "AI" if str.downcase == "ai"
110
+ return "ML" if str.downcase == "ml"
111
+ return "LLM" if str.downcase == "llm"
112
+ str.split(/[-_]/).map(&:capitalize).join
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # ImageEditor generator for creating new image editors
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:image_editor Product
10
+ # rails generate ruby_llm_agents:image_editor Background --model gpt-image-1 --size 1024x1024
11
+ # rails generate ruby_llm_agents:image_editor Photo --content_policy strict
12
+ # rails generate ruby_llm_agents:image_editor Product --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/image/editors/product_editor.rb
16
+ #
17
+ class ImageEditorGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :model, type: :string, default: "gpt-image-1",
21
+ desc: "The image model to use"
22
+ class_option :size, type: :string, default: "1024x1024",
23
+ desc: "Output image size (e.g., 1024x1024)"
24
+ class_option :content_policy, type: :string, default: "standard",
25
+ desc: "Content policy level (none, standard, moderate, strict)"
26
+ class_option :cache, type: :string, default: nil,
27
+ desc: "Cache TTL (e.g., '1.hour', '1.day')"
28
+ class_option :root,
29
+ type: :string,
30
+ default: nil,
31
+ desc: "Root directory name (default: uses config or 'llm')"
32
+ class_option :namespace,
33
+ type: :string,
34
+ default: nil,
35
+ desc: "Root namespace (default: camelized root or config)"
36
+
37
+ def ensure_base_class_and_skill_file
38
+ @root_namespace = root_namespace
39
+ @image_namespace = "#{root_namespace}::Image"
40
+ editors_dir = "app/#{root_directory}/image/editors"
41
+
42
+ # Create directory if needed
43
+ empty_directory editors_dir
44
+
45
+ # Create base class if it doesn't exist
46
+ base_class_path = "#{editors_dir}/application_image_editor.rb"
47
+ unless File.exist?(File.join(destination_root, base_class_path))
48
+ template "application_image_editor.rb.tt", base_class_path
49
+ end
50
+
51
+ # Create skill file if it doesn't exist
52
+ skill_file_path = "#{editors_dir}/IMAGE_EDITORS.md"
53
+ unless File.exist?(File.join(destination_root, skill_file_path))
54
+ template "skills/IMAGE_EDITORS.md.tt", skill_file_path
55
+ end
56
+ end
57
+
58
+ def create_image_editor_file
59
+ @root_namespace = root_namespace
60
+ @image_namespace = "#{root_namespace}::Image"
61
+ editor_path = name.underscore
62
+ template "image_editor.rb.tt", "app/#{root_directory}/image/editors/#{editor_path}_editor.rb"
63
+ end
64
+
65
+ def show_usage
66
+ editor_class_name = name.split("/").map(&:camelize).join("::")
67
+ full_class_name = "#{root_namespace}::Image::#{editor_class_name}Editor"
68
+ say ""
69
+ say "Image editor #{full_class_name} created!", :green
70
+ say ""
71
+ say "Usage:"
72
+ say " # Edit an image with a mask"
73
+ say " result = #{full_class_name}.call("
74
+ say " image: 'photo.png',"
75
+ say " mask: 'mask.png',"
76
+ say " prompt: 'Replace the background with a beach scene'"
77
+ say " )"
78
+ say " result.url # => 'https://...'"
79
+ say ""
80
+ say " # Generate multiple edit variations"
81
+ say " result = #{full_class_name}.call("
82
+ say " image: 'photo.png',"
83
+ say " mask: 'mask.png',"
84
+ say " prompt: 'Add a sunset',"
85
+ say " count: 3"
86
+ say " )"
87
+ say " result.urls # => ['https://...', ...]"
88
+ say ""
89
+ end
90
+
91
+ private
92
+
93
+ def root_directory
94
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
95
+ end
96
+
97
+ def root_namespace
98
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
99
+ end
100
+
101
+ def camelize(str)
102
+ return "AI" if str.downcase == "ai"
103
+ return "ML" if str.downcase == "ml"
104
+ return "LLM" if str.downcase == "llm"
105
+ str.split(/[-_]/).map(&:capitalize).join
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # ImageGenerator generator for creating new image generators
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:image_generator Logo
10
+ # rails generate ruby_llm_agents:image_generator Product --model gpt-image-1 --size 1024x1024
11
+ # rails generate ruby_llm_agents:image_generator Avatar --quality hd --style vivid
12
+ # rails generate ruby_llm_agents:image_generator Logo --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/image/generators/logo_generator.rb
16
+ #
17
+ class ImageGeneratorGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :model, type: :string, default: "gpt-image-1",
21
+ desc: "The image generation model to use"
22
+ class_option :size, type: :string, default: "1024x1024",
23
+ desc: "Image size (e.g., 1024x1024, 1792x1024)"
24
+ class_option :quality, type: :string, default: "standard",
25
+ desc: "Image quality (standard, hd)"
26
+ class_option :style, type: :string, default: "vivid",
27
+ desc: "Image style (vivid, natural)"
28
+ class_option :content_policy, type: :string, default: "standard",
29
+ desc: "Content policy level (none, standard, moderate, strict)"
30
+ class_option :cache, type: :string, default: nil,
31
+ desc: "Cache TTL (e.g., '1.hour', '1.day')"
32
+ class_option :root,
33
+ type: :string,
34
+ default: nil,
35
+ desc: "Root directory name (default: uses config or 'llm')"
36
+ class_option :namespace,
37
+ type: :string,
38
+ default: nil,
39
+ desc: "Root namespace (default: camelized root or config)"
40
+
41
+ def ensure_base_class_and_skill_file
42
+ @root_namespace = root_namespace
43
+ @image_namespace = "#{root_namespace}::Image"
44
+ generators_dir = "app/#{root_directory}/image/generators"
45
+
46
+ # Create directory if needed
47
+ empty_directory generators_dir
48
+
49
+ # Create base class if it doesn't exist
50
+ base_class_path = "#{generators_dir}/application_image_generator.rb"
51
+ unless File.exist?(File.join(destination_root, base_class_path))
52
+ template "application_image_generator.rb.tt", base_class_path
53
+ end
54
+
55
+ # Create skill file if it doesn't exist
56
+ skill_file_path = "#{generators_dir}/IMAGE_GENERATORS.md"
57
+ unless File.exist?(File.join(destination_root, skill_file_path))
58
+ template "skills/IMAGE_GENERATORS.md.tt", skill_file_path
59
+ end
60
+ end
61
+
62
+ def create_image_generator_file
63
+ # Support nested paths: "product/hero" -> "app/{root}/image/generators/product/hero_generator.rb"
64
+ @root_namespace = root_namespace
65
+ @image_namespace = "#{root_namespace}::Image"
66
+ generator_path = name.underscore
67
+ template "image_generator.rb.tt", "app/#{root_directory}/image/generators/#{generator_path}_generator.rb"
68
+ end
69
+
70
+ def show_usage
71
+ # Build full class name from path
72
+ generator_class_name = name.split("/").map(&:camelize).join("::")
73
+ full_class_name = "#{root_namespace}::Image::#{generator_class_name}Generator"
74
+ say ""
75
+ say "Image generator #{full_class_name} created!", :green
76
+ say ""
77
+ say "Usage:"
78
+ say " # Generate a single image"
79
+ say " result = #{full_class_name}.call(prompt: \"A beautiful sunset\")"
80
+ say " result.url # => \"https://...\""
81
+ say " result.save(\"sunset.png\")"
82
+ say ""
83
+ say " # Generate multiple images"
84
+ say " result = #{full_class_name}.call(prompt: \"Logos\", count: 4)"
85
+ say " result.urls # => [\"https://...\", ...]"
86
+ say ""
87
+ say " # Override settings at runtime"
88
+ say " result = #{full_class_name}.call("
89
+ say " prompt: \"High quality portrait\","
90
+ say " quality: \"hd\","
91
+ say " size: \"1792x1024\""
92
+ say " )"
93
+ say ""
94
+ end
95
+
96
+ private
97
+
98
+ def root_directory
99
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
100
+ end
101
+
102
+ def root_namespace
103
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
104
+ end
105
+
106
+ def camelize(str)
107
+ # Handle special cases for common abbreviations
108
+ return "AI" if str.downcase == "ai"
109
+ return "ML" if str.downcase == "ml"
110
+ return "LLM" if str.downcase == "llm"
111
+
112
+ # Standard camelization
113
+ str.split(/[-_]/).map(&:capitalize).join
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module RubyLlmAgents
6
+ # ImagePipeline generator for creating new image pipelines
7
+ #
8
+ # Usage:
9
+ # rails generate ruby_llm_agents:image_pipeline Product
10
+ # rails generate ruby_llm_agents:image_pipeline Ecommerce --steps generate,upscale,remove_background
11
+ # rails generate ruby_llm_agents:image_pipeline Content --steps generate,analyze
12
+ # rails generate ruby_llm_agents:image_pipeline Product --root=ai
13
+ #
14
+ # This will create:
15
+ # - app/{root}/image/pipelines/product_pipeline.rb
16
+ #
17
+ class ImagePipelineGenerator < ::Rails::Generators::NamedBase
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ class_option :steps, type: :string, default: "generate,upscale",
21
+ desc: "Pipeline steps (comma-separated: generate,upscale,transform,analyze,remove_background)"
22
+ class_option :stop_on_error, type: :boolean, default: true,
23
+ desc: "Stop pipeline on first error"
24
+ class_option :cache, type: :string, default: nil,
25
+ desc: "Cache TTL (e.g., '1.hour', '1.day')"
26
+ class_option :root,
27
+ type: :string,
28
+ default: nil,
29
+ desc: "Root directory name (default: uses config or 'llm')"
30
+ class_option :namespace,
31
+ type: :string,
32
+ default: nil,
33
+ desc: "Root namespace (default: camelized root or config)"
34
+
35
+ def ensure_base_class_and_skill_file
36
+ @root_namespace = root_namespace
37
+ @image_namespace = "#{root_namespace}::Image"
38
+ pipelines_dir = "app/#{root_directory}/image/pipelines"
39
+
40
+ # Create directory if needed
41
+ empty_directory pipelines_dir
42
+
43
+ # Create base class if it doesn't exist
44
+ base_class_path = "#{pipelines_dir}/application_image_pipeline.rb"
45
+ unless File.exist?(File.join(destination_root, base_class_path))
46
+ template "application_image_pipeline.rb.tt", base_class_path
47
+ end
48
+
49
+ # Create skill file if it doesn't exist
50
+ skill_file_path = "#{pipelines_dir}/IMAGE_PIPELINES.md"
51
+ unless File.exist?(File.join(destination_root, skill_file_path))
52
+ template "skills/IMAGE_PIPELINES.md.tt", skill_file_path
53
+ end
54
+ end
55
+
56
+ def create_image_pipeline_file
57
+ @root_namespace = root_namespace
58
+ @image_namespace = "#{root_namespace}::Image"
59
+ pipeline_path = name.underscore
60
+ template "image_pipeline.rb.tt", "app/#{root_directory}/image/pipelines/#{pipeline_path}_pipeline.rb"
61
+ end
62
+
63
+ def create_step_classes
64
+ # Create stub classes for referenced steps if they don't exist
65
+ parsed_steps.each do |step|
66
+ create_step_stub(step) if should_create_stub?(step)
67
+ end
68
+ end
69
+
70
+ def show_usage
71
+ pipeline_class_name = name.split("/").map(&:camelize).join("::")
72
+ full_class_name = "#{root_namespace}::Image::#{pipeline_class_name}Pipeline"
73
+ say ""
74
+ say "Image pipeline #{full_class_name} created!", :green
75
+ say ""
76
+ say "Usage:"
77
+ say " # Run the pipeline"
78
+ say " result = #{full_class_name}.call(prompt: 'Product photo')"
79
+ say " result.final_image # => The processed image"
80
+ say " result.total_cost # => Combined cost of all steps"
81
+ say " result.step_count # => Number of steps executed"
82
+ say ""
83
+ say " # Access individual step results"
84
+ say " result.step(:generate) # => ImageGenerationResult"
85
+ say " result.step(:upscale) # => ImageUpscaleResult"
86
+ say " result.analysis # => ImageAnalysisResult (if analyzer step)"
87
+ say ""
88
+ say " # Save the final image"
89
+ say " result.save('output.png')"
90
+ say ""
91
+ say " # With tenant tracking"
92
+ say " result = #{full_class_name}.call("
93
+ say " prompt: 'Product photo',"
94
+ say " tenant: current_organization"
95
+ say " )"
96
+ say ""
97
+ end
98
+
99
+ private
100
+
101
+ def root_directory
102
+ @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
103
+ end
104
+
105
+ def root_namespace
106
+ @root_namespace ||= options[:namespace] || camelize(root_directory)
107
+ end
108
+
109
+ def camelize(str)
110
+ return "AI" if str.downcase == "ai"
111
+ return "ML" if str.downcase == "ml"
112
+ return "LLM" if str.downcase == "llm"
113
+ str.split(/[-_]/).map(&:capitalize).join
114
+ end
115
+
116
+ def parsed_steps
117
+ options[:steps].to_s.split(",").map(&:strip).map(&:to_sym)
118
+ end
119
+
120
+ def should_create_stub?(step)
121
+ case step
122
+ when :generate
123
+ !File.exist?("app/#{root_directory}/image/generators/#{name.underscore}_generator.rb")
124
+ when :upscale
125
+ !File.exist?("app/#{root_directory}/image/upscalers/#{name.underscore}_upscaler.rb")
126
+ when :transform
127
+ !File.exist?("app/#{root_directory}/image/transformers/#{name.underscore}_transformer.rb")
128
+ when :analyze
129
+ !File.exist?("app/#{root_directory}/image/analyzers/#{name.underscore}_analyzer.rb")
130
+ when :remove_background
131
+ !File.exist?("app/#{root_directory}/image/background_removers/#{name.underscore}_background_remover.rb")
132
+ else
133
+ false
134
+ end
135
+ end
136
+
137
+ def create_step_stub(step)
138
+ # Just show a note - don't auto-create stubs
139
+ case step
140
+ when :generate
141
+ say " Note: You may want to create #{name}Generator with:", :yellow
142
+ say " rails generate ruby_llm_agents:image_generator #{name}"
143
+ when :upscale
144
+ say " Note: You may want to create #{name}Upscaler with:", :yellow
145
+ say " rails generate ruby_llm_agents:image_upscaler #{name}"
146
+ when :transform
147
+ say " Note: You may want to create #{name}Transformer with:", :yellow
148
+ say " rails generate ruby_llm_agents:image_transformer #{name}"
149
+ when :analyze
150
+ say " Note: You may want to create #{name}Analyzer with:", :yellow
151
+ say " rails generate ruby_llm_agents:image_analyzer #{name}"
152
+ when :remove_background
153
+ say " Note: You may want to create #{name}BackgroundRemover with:", :yellow
154
+ say " rails generate ruby_llm_agents:background_remover #{name}"
155
+ end
156
+ end
157
+
158
+ def step_classes
159
+ @step_classes ||= parsed_steps.map do |step|
160
+ class_base = name.split("/").map(&:camelize).join("::")
161
+ case step
162
+ when :generate
163
+ { step: step, type: :generator, class_name: "#{@image_namespace}::#{class_base}Generator" }
164
+ when :upscale
165
+ { step: step, type: :upscaler, class_name: "#{@image_namespace}::#{class_base}Upscaler" }
166
+ when :transform
167
+ { step: step, type: :transformer, class_name: "#{@image_namespace}::#{class_base}Transformer" }
168
+ when :analyze
169
+ { step: step, type: :analyzer, class_name: "#{@image_namespace}::#{class_base}Analyzer" }
170
+ when :remove_background
171
+ { step: step, type: :remover, class_name: "#{@image_namespace}::#{class_base}BackgroundRemover" }
172
+ else
173
+ { step: step, type: step, class_name: "#{@image_namespace}::#{class_base}#{step.to_s.camelize}" }
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end