ruby_llm-agents 0.4.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 (208) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +225 -34
  3. data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
  4. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
  6. data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
  8. data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
  9. data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
  10. data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
  11. data/app/models/ruby_llm/agents/execution.rb +3 -0
  12. data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
  13. data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
  14. data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
  15. data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
  16. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
  17. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
  18. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
  19. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
  20. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
  21. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
  22. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
  23. data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
  24. data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
  25. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
  26. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
  27. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
  28. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
  30. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
  31. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
  32. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
  33. data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
  34. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
  35. data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
  36. data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
  37. data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
  38. data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
  39. data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
  40. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
  41. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
  42. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
  43. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
  44. data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
  45. data/config/routes.rb +13 -1
  46. data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
  47. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
  48. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
  49. data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
  50. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
  51. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
  52. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
  53. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
  54. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
  55. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
  56. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
  57. data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
  58. data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
  59. data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
  60. data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
  61. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
  62. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
  63. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
  64. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
  65. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
  66. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
  67. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
  68. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
  69. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
  70. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
  71. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
  72. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
  73. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
  74. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
  75. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
  76. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
  77. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
  78. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
  79. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
  80. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
  81. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
  82. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
  83. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
  84. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
  85. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
  86. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
  87. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
  88. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
  89. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
  90. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
  91. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
  92. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
  93. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
  94. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
  95. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
  96. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
  97. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
  98. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
  99. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
  100. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
  101. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
  102. data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
  103. data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
  104. data/lib/ruby_llm/agents/base_agent.rb +675 -0
  105. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
  106. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
  107. data/lib/ruby_llm/agents/core/base.rb +135 -0
  108. data/lib/ruby_llm/agents/core/configuration.rb +981 -0
  109. data/lib/ruby_llm/agents/core/errors.rb +150 -0
  110. data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
  111. data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
  112. data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
  113. data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
  114. data/lib/ruby_llm/agents/dsl/base.rb +110 -0
  115. data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
  116. data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
  117. data/lib/ruby_llm/agents/dsl.rb +41 -0
  118. data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
  119. data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
  120. data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
  121. data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
  122. data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
  123. data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
  124. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
  125. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
  126. data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
  127. data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
  128. data/lib/ruby_llm/agents/image/editor.rb +92 -0
  129. data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
  130. data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
  131. data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
  132. data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
  133. data/lib/ruby_llm/agents/image/generator.rb +455 -0
  134. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
  135. data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
  136. data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
  137. data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
  138. data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
  139. data/lib/ruby_llm/agents/image/transformer.rb +95 -0
  140. data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
  141. data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
  142. data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
  143. data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
  144. data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
  145. data/lib/ruby_llm/agents/image/variator.rb +80 -0
  146. data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
  147. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
  148. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
  149. data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
  150. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
  151. data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
  152. data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
  153. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
  154. data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
  155. data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
  156. data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
  157. data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
  158. data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
  159. data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
  160. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
  161. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
  162. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
  163. data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
  164. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
  165. data/lib/ruby_llm/agents/pipeline.rb +68 -0
  166. data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
  167. data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
  168. data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
  169. data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
  170. data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
  171. data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
  172. data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
  173. data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
  174. data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
  175. data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
  176. data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
  177. data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
  178. data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
  179. data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
  180. data/lib/ruby_llm/agents/text/embedder.rb +444 -0
  181. data/lib/ruby_llm/agents/text/moderator.rb +237 -0
  182. data/lib/ruby_llm/agents/workflow/async.rb +220 -0
  183. data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
  184. data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
  185. data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
  186. data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
  187. data/lib/ruby_llm/agents.rb +86 -20
  188. metadata +189 -35
  189. data/lib/ruby_llm/agents/base/caching.rb +0 -40
  190. data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
  191. data/lib/ruby_llm/agents/base/dsl.rb +0 -324
  192. data/lib/ruby_llm/agents/base/execution.rb +0 -283
  193. data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
  194. data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
  195. data/lib/ruby_llm/agents/base/response_building.rb +0 -86
  196. data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
  197. data/lib/ruby_llm/agents/base.rb +0 -209
  198. data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
  199. data/lib/ruby_llm/agents/configuration.rb +0 -357
  200. /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
  201. /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
  202. /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
  203. /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
  204. /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
  205. /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
  206. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
  207. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
  208. /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module RubyLLM
6
+ module Agents
7
+ # Result object for text-to-speech operations
8
+ #
9
+ # Wraps audio output with metadata about the operation including
10
+ # duration, format, cost, and utility methods for saving audio.
11
+ #
12
+ # @example Basic usage
13
+ # result = ArticleNarrator.call(text: "Hello world")
14
+ # result.audio # => Binary audio data
15
+ # result.duration # => 1.5 (seconds)
16
+ # result.total_cost # => 0.0003
17
+ #
18
+ # @example Saving audio
19
+ # result.save_to("/path/to/output.mp3")
20
+ #
21
+ # @example Base64 encoding
22
+ # result.to_base64 # => "//uQx..."
23
+ #
24
+ # @api public
25
+ class SpeechResult
26
+ # @!group Audio Content
27
+
28
+ # @!attribute [r] audio
29
+ # @return [String, nil] Binary audio data
30
+ attr_reader :audio
31
+
32
+ # @!attribute [r] audio_url
33
+ # @return [String, nil] URL if audio was stored remotely
34
+ attr_reader :audio_url
35
+
36
+ # @!attribute [r] audio_key
37
+ # @return [String, nil] Storage key if stored
38
+ attr_reader :audio_key
39
+
40
+ # @!attribute [r] audio_path
41
+ # @return [String, nil] Local file path if saved
42
+ attr_reader :audio_path
43
+
44
+ # @!endgroup
45
+
46
+ # @!group Audio Metadata
47
+
48
+ # @!attribute [r] duration
49
+ # @return [Float, nil] Duration in seconds
50
+ attr_reader :duration
51
+
52
+ # @!attribute [r] format
53
+ # @return [Symbol, nil] Audio format (:mp3, :wav, :ogg, etc.)
54
+ attr_reader :format
55
+
56
+ # @!attribute [r] sample_rate
57
+ # @return [Integer, nil] Sample rate in Hz
58
+ attr_reader :sample_rate
59
+
60
+ # @!attribute [r] bitrate
61
+ # @return [Integer, nil] Bitrate in kbps
62
+ attr_reader :bitrate
63
+
64
+ # @!attribute [r] file_size
65
+ # @return [Integer, nil] Size in bytes
66
+ attr_reader :file_size
67
+
68
+ # @!endgroup
69
+
70
+ # @!group Input Metadata
71
+
72
+ # @!attribute [r] characters
73
+ # @return [Integer, nil] Character count (for billing)
74
+ attr_reader :characters
75
+
76
+ # @!attribute [r] text_length
77
+ # @return [Integer, nil] Original text length
78
+ attr_reader :text_length
79
+
80
+ # @!endgroup
81
+
82
+ # @!group Voice Info
83
+
84
+ # @!attribute [r] provider
85
+ # @return [Symbol, nil] Provider (:openai, :elevenlabs, :google, :polly)
86
+ attr_reader :provider
87
+
88
+ # @!attribute [r] model_id
89
+ # @return [String, nil] Model identifier
90
+ attr_reader :model_id
91
+
92
+ # @!attribute [r] voice_id
93
+ # @return [String, nil] Voice identifier
94
+ attr_reader :voice_id
95
+
96
+ # @!attribute [r] voice_name
97
+ # @return [String, nil] Voice display name
98
+ attr_reader :voice_name
99
+
100
+ # @!endgroup
101
+
102
+ # @!group Timing
103
+
104
+ # @!attribute [r] duration_ms
105
+ # @return [Integer, nil] Execution duration in milliseconds
106
+ attr_reader :duration_ms
107
+
108
+ # @!attribute [r] started_at
109
+ # @return [Time, nil] When execution started
110
+ attr_reader :started_at
111
+
112
+ # @!attribute [r] completed_at
113
+ # @return [Time, nil] When execution completed
114
+ attr_reader :completed_at
115
+
116
+ # @!endgroup
117
+
118
+ # @!group Cost & Usage
119
+
120
+ # @!attribute [r] total_cost
121
+ # @return [Float, nil] Total cost in USD
122
+ attr_reader :total_cost
123
+
124
+ # @!endgroup
125
+
126
+ # @!group Status
127
+
128
+ # @!attribute [r] status
129
+ # @return [Symbol] Status (:success, :partial, :failed)
130
+ attr_reader :status
131
+
132
+ # @!endgroup
133
+
134
+ # @!group Multi-tenancy
135
+
136
+ # @!attribute [r] tenant_id
137
+ # @return [String, nil] Tenant identifier if multi-tenancy enabled
138
+ attr_reader :tenant_id
139
+
140
+ # @!endgroup
141
+
142
+ # @!group Error
143
+
144
+ # @!attribute [r] error_class
145
+ # @return [String, nil] Exception class name if failed
146
+ attr_reader :error_class
147
+
148
+ # @!attribute [r] error_message
149
+ # @return [String, nil] Exception message if failed
150
+ attr_reader :error_message
151
+
152
+ # @!endgroup
153
+
154
+ # Creates a new SpeechResult instance
155
+ #
156
+ # @param attributes [Hash] Result attributes
157
+ # @option attributes [String] :audio Binary audio data
158
+ # @option attributes [String] :audio_url URL if stored remotely
159
+ # @option attributes [String] :audio_key Storage key
160
+ # @option attributes [String] :audio_path Local file path
161
+ # @option attributes [Float] :duration Duration in seconds
162
+ # @option attributes [Symbol] :format Audio format
163
+ # @option attributes [Integer] :sample_rate Sample rate in Hz
164
+ # @option attributes [Integer] :bitrate Bitrate in kbps
165
+ # @option attributes [Integer] :file_size Size in bytes
166
+ # @option attributes [Integer] :characters Character count
167
+ # @option attributes [Integer] :text_length Original text length
168
+ # @option attributes [Symbol] :provider Provider name
169
+ # @option attributes [String] :model_id Model identifier
170
+ # @option attributes [String] :voice_id Voice identifier
171
+ # @option attributes [String] :voice_name Voice display name
172
+ # @option attributes [Integer] :duration_ms Execution duration
173
+ # @option attributes [Time] :started_at Start time
174
+ # @option attributes [Time] :completed_at Completion time
175
+ # @option attributes [Float] :total_cost Cost in USD
176
+ # @option attributes [Symbol] :status Status
177
+ # @option attributes [String] :tenant_id Tenant identifier
178
+ # @option attributes [String] :error_class Error class
179
+ # @option attributes [String] :error_message Error message
180
+ def initialize(attributes = {})
181
+ # Audio content
182
+ @audio = attributes[:audio]
183
+ @audio_url = attributes[:audio_url]
184
+ @audio_key = attributes[:audio_key]
185
+ @audio_path = attributes[:audio_path]
186
+
187
+ # Audio metadata
188
+ @duration = attributes[:duration]
189
+ @format = attributes[:format]
190
+ @sample_rate = attributes[:sample_rate]
191
+ @bitrate = attributes[:bitrate]
192
+ @file_size = attributes[:file_size] || @audio&.bytesize
193
+
194
+ # Input metadata
195
+ @characters = attributes[:characters]
196
+ @text_length = attributes[:text_length]
197
+
198
+ # Voice info
199
+ @provider = attributes[:provider]
200
+ @model_id = attributes[:model_id]
201
+ @voice_id = attributes[:voice_id]
202
+ @voice_name = attributes[:voice_name]
203
+
204
+ # Timing
205
+ @duration_ms = attributes[:duration_ms]
206
+ @started_at = attributes[:started_at]
207
+ @completed_at = attributes[:completed_at]
208
+
209
+ # Cost & usage
210
+ @total_cost = attributes[:total_cost]
211
+
212
+ # Status
213
+ @status = attributes[:status] || :success
214
+
215
+ # Multi-tenancy
216
+ @tenant_id = attributes[:tenant_id]
217
+
218
+ # Error
219
+ @error_class = attributes[:error_class]
220
+ @error_message = attributes[:error_message]
221
+ end
222
+
223
+ # Returns whether the speech generation succeeded
224
+ #
225
+ # @return [Boolean] true if no error occurred
226
+ def success?
227
+ error_class.nil? && status == :success
228
+ end
229
+
230
+ # Returns whether the speech generation failed
231
+ #
232
+ # @return [Boolean] true if an error occurred
233
+ def error?
234
+ !success?
235
+ end
236
+
237
+ # Saves the audio to a file
238
+ #
239
+ # @param path [String] File path to save to
240
+ # @return [String] The path where audio was saved
241
+ # @raise [StandardError] If audio data is not available
242
+ def save_to(path)
243
+ raise StandardError, "No audio data available" unless audio
244
+
245
+ File.binwrite(path, audio)
246
+ @audio_path = path
247
+ path
248
+ end
249
+
250
+ # Returns the audio as a Base64-encoded string
251
+ #
252
+ # @return [String, nil] Base64-encoded audio or nil if no audio
253
+ def to_base64
254
+ return nil unless audio
255
+
256
+ Base64.strict_encode64(audio)
257
+ end
258
+
259
+ # Returns the audio as a data URI
260
+ #
261
+ # @return [String, nil] Data URI or nil if no audio
262
+ def to_data_uri
263
+ return nil unless audio
264
+
265
+ mime_type = mime_type_for_format
266
+ "data:#{mime_type};base64,#{to_base64}"
267
+ end
268
+
269
+ # Returns words per second of generated audio
270
+ #
271
+ # @return [Float, nil] Words per second or nil if not calculable
272
+ def words_per_second
273
+ return nil unless text_length && duration && duration > 0
274
+
275
+ # Rough estimate: average word length is 5 characters
276
+ word_count = text_length / 5.0
277
+ word_count / duration
278
+ end
279
+
280
+ # Converts the result to a hash
281
+ #
282
+ # @return [Hash] All result data as a hash
283
+ def to_h
284
+ {
285
+ audio_url: audio_url,
286
+ audio_key: audio_key,
287
+ audio_path: audio_path,
288
+ duration: duration,
289
+ format: format,
290
+ sample_rate: sample_rate,
291
+ bitrate: bitrate,
292
+ file_size: file_size,
293
+ characters: characters,
294
+ text_length: text_length,
295
+ provider: provider,
296
+ model_id: model_id,
297
+ voice_id: voice_id,
298
+ voice_name: voice_name,
299
+ duration_ms: duration_ms,
300
+ started_at: started_at,
301
+ completed_at: completed_at,
302
+ total_cost: total_cost,
303
+ status: status,
304
+ tenant_id: tenant_id,
305
+ error_class: error_class,
306
+ error_message: error_message
307
+ # Note: audio binary data excluded for serialization safety
308
+ }
309
+ end
310
+
311
+ private
312
+
313
+ # Returns MIME type for the audio format
314
+ #
315
+ # @return [String] MIME type
316
+ def mime_type_for_format
317
+ case format
318
+ when :mp3
319
+ "audio/mpeg"
320
+ when :wav
321
+ "audio/wav"
322
+ when :ogg
323
+ "audio/ogg"
324
+ when :flac
325
+ "audio/flac"
326
+ when :aac
327
+ "audio/aac"
328
+ when :opus
329
+ "audio/opus"
330
+ when :pcm
331
+ "audio/pcm"
332
+ else
333
+ "audio/mpeg" # Default to mp3
334
+ end
335
+ end
336
+ end
337
+ end
338
+ end