activeagent 0.0.0 → 0.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -0
  3. data/Rakefile +3 -0
  4. data/lib/active_agent/README.md +21 -0
  5. data/lib/active_agent/action_prompt/README.md +44 -0
  6. data/lib/active_agent/action_prompt/base.rb +0 -0
  7. data/lib/active_agent/action_prompt/collector.rb +34 -0
  8. data/lib/active_agent/action_prompt/message.rb +44 -0
  9. data/lib/active_agent/action_prompt/prompt.rb +79 -0
  10. data/lib/active_agent/action_prompt.rb +127 -0
  11. data/lib/active_agent/base.rb +439 -0
  12. data/lib/active_agent/callbacks.rb +31 -0
  13. data/lib/active_agent/deprecator.rb +7 -0
  14. data/lib/active_agent/engine.rb +14 -0
  15. data/lib/active_agent/generation.rb +78 -0
  16. data/lib/active_agent/generation_job.rb +47 -0
  17. data/lib/active_agent/generation_methods.rb +60 -0
  18. data/lib/active_agent/generation_provider/README.md +17 -0
  19. data/lib/active_agent/generation_provider/base.rb +36 -0
  20. data/lib/active_agent/generation_provider/open_ai_provider.rb +68 -0
  21. data/lib/active_agent/generation_provider/response.rb +15 -0
  22. data/lib/active_agent/generation_provider.rb +63 -0
  23. data/lib/active_agent/inline_preview_interceptor.rb +60 -0
  24. data/lib/active_agent/log_subscriber.rb +44 -0
  25. data/lib/active_agent/operation.rb +13 -0
  26. data/lib/active_agent/parameterized.rb +66 -0
  27. data/lib/active_agent/preview.rb +133 -0
  28. data/lib/active_agent/prompt_helper.rb +19 -0
  29. data/lib/active_agent/queued_generation.rb +12 -0
  30. data/lib/active_agent/railtie.rb +89 -0
  31. data/lib/active_agent/rescuable.rb +34 -0
  32. data/lib/active_agent/service.rb +25 -0
  33. data/lib/active_agent/test_case.rb +125 -0
  34. data/lib/active_agent/version.rb +3 -0
  35. data/lib/active_agent.rb +63 -0
  36. data/lib/generators/active_agent/USAGE +18 -0
  37. data/lib/generators/active_agent/agent_generator.rb +63 -0
  38. data/lib/generators/active_agent/templates/action.html.erb.tt +0 -0
  39. data/lib/generators/active_agent/templates/action.json.jbuilder.tt +33 -0
  40. data/lib/generators/active_agent/templates/agent.rb.tt +14 -0
  41. data/lib/generators/active_agent/templates/agent_spec.rb.tt +0 -0
  42. data/lib/generators/active_agent/templates/agent_test.rb.tt +0 -0
  43. data/lib/generators/active_agent/templates/application_agent.rb.tt +4 -0
  44. metadata +129 -4
@@ -0,0 +1,439 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_agent/prompt_helper"
4
+ require "active_agent/action_prompt/prompt"
5
+ require "active_agent/action_prompt/collector"
6
+ require "active_support/core_ext/string/inflections"
7
+ require "active_support/core_ext/hash/except"
8
+ require "active_support/core_ext/module/anonymous"
9
+
10
+ # require "active_agent/log_subscriber"
11
+ require "active_agent/rescuable"
12
+
13
+ # The ActiveAgent module provides a framework for creating agents that can generate content
14
+ # and handle various actions. The Base class within this module extends AbstractController::Base
15
+ # and includes several modules to provide additional functionality such as callbacks, generation
16
+ # methods, and rescuable actions.
17
+ #
18
+ # The Base class defines several class methods for registering and unregistering observers and
19
+ # interceptors, as well as methods for generating content with a specified provider and streaming
20
+ # content. It also provides methods for setting default parameters and handling prompts.
21
+ #
22
+ # The instance methods in the Base class include methods for performing generation, processing
23
+ # actions, and handling headers and attachments. The class also defines a NullPrompt class for
24
+ # handling cases where no prompt is provided.
25
+ #
26
+ # The Base class uses ActiveSupport::Notifications for instrumentation and provides several
27
+ # private methods for setting payloads, applying defaults, and collecting responses from blocks,
28
+ # text, or templates.
29
+ #
30
+ # The class also includes several protected instance variables and defines hooks for loading
31
+ # additional functionality.
32
+ module ActiveAgent
33
+ class Base < AbstractController::Base
34
+ include Callbacks
35
+ include GenerationMethods
36
+ include GenerationProvider
37
+ include QueuedGeneration
38
+ include Rescuable
39
+ include Parameterized
40
+ include Previews
41
+ # include FormBuilder
42
+
43
+ abstract!
44
+
45
+ include AbstractController::Rendering
46
+
47
+ include AbstractController::Logger
48
+ include AbstractController::Helpers
49
+ include AbstractController::Translation
50
+ include AbstractController::AssetPaths
51
+ include AbstractController::Callbacks
52
+ include AbstractController::Caching
53
+
54
+ include ActionView::Layouts
55
+
56
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
57
+
58
+ helper ActiveAgent::PromptHelper
59
+
60
+ class_attribute :options
61
+
62
+ class_attribute :default_params, default: {
63
+ mime_version: "1.0",
64
+ charset: "UTF-8",
65
+ content_type: "text/plain",
66
+ parts_order: ["text/plain", "text/enriched", "text/html"]
67
+ }.freeze
68
+
69
+ class << self
70
+ def prompt(...)
71
+ new.prompt(...)
72
+ end
73
+
74
+ # Register one or more Observers which will be notified when mail is delivered.
75
+ def register_observers(*observers)
76
+ observers.flatten.compact.each { |observer| register_observer(observer) }
77
+ end
78
+
79
+ # Unregister one or more previously registered Observers.
80
+ def unregister_observers(*observers)
81
+ observers.flatten.compact.each { |observer| unregister_observer(observer) }
82
+ end
83
+
84
+ # Register one or more Interceptors which will be called before mail is sent.
85
+ def register_interceptors(*interceptors)
86
+ interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
87
+ end
88
+
89
+ # Unregister one or more previously registered Interceptors.
90
+ def unregister_interceptors(*interceptors)
91
+ interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) }
92
+ end
93
+
94
+ # Register an Observer which will be notified when mail is delivered.
95
+ # Either a class, string, or symbol can be passed in as the Observer.
96
+ # If a string or symbol is passed in it will be camelized and constantized.
97
+ def register_observer(observer)
98
+ Mail.register_observer(observer_class_for(observer))
99
+ end
100
+
101
+ # Unregister a previously registered Observer.
102
+ # Either a class, string, or symbol can be passed in as the Observer.
103
+ # If a string or symbol is passed in it will be camelized and constantized.
104
+ def unregister_observer(observer)
105
+ Mail.unregister_observer(observer_class_for(observer))
106
+ end
107
+
108
+ # Register an Interceptor which will be called before mail is sent.
109
+ # Either a class, string, or symbol can be passed in as the Interceptor.
110
+ # If a string or symbol is passed in it will be camelized and constantized.
111
+ def register_interceptor(interceptor)
112
+ Mail.register_interceptor(observer_class_for(interceptor))
113
+ end
114
+
115
+ # Unregister a previously registered Interceptor.
116
+ # Either a class, string, or symbol can be passed in as the Interceptor.
117
+ # If a string or symbol is passed in it will be camelized and constantized.
118
+ def unregister_interceptor(interceptor)
119
+ Mail.unregister_interceptor(observer_class_for(interceptor))
120
+ end
121
+
122
+ def observer_class_for(value) # :nodoc:
123
+ case value
124
+ when String, Symbol
125
+ value.to_s.camelize.constantize
126
+ else
127
+ value
128
+ end
129
+ end
130
+ private :observer_class_for
131
+
132
+ # Define how the agent should generate content
133
+ def generate_with(provider, **options)
134
+ self.generation_provider = provider
135
+ self.options = (options || {}).merge(options)
136
+ end
137
+
138
+ def stream_with(&stream)
139
+ self.options = (options || {}).merge(stream: stream)
140
+ end
141
+
142
+ # Returns the name of the current agent. This method is also being used as a path for a view lookup.
143
+ # If this is an anonymous agent, this method will return +anonymous+ instead.
144
+ def agent_name
145
+ @agent_name ||= anonymous? ? "anonymous" : name.underscore
146
+ end
147
+ # Allows to set the name of current agent.
148
+ attr_writer :agent_name
149
+ alias_method :controller_path, :agent_name
150
+
151
+ # Sets the defaults through app configuration:
152
+ #
153
+ # config.action_agent.default(from: "no-reply@example.org")
154
+ #
155
+ # Aliased by ::default_options=
156
+ def default(value = nil)
157
+ self.default_params = default_params.merge(value).freeze if value
158
+ default_params
159
+ end
160
+ # Allows to set defaults through app configuration:
161
+ #
162
+ # config.action_agent.default_options = { from: "no-reply@example.org" }
163
+ alias_method :default_options=, :default
164
+
165
+ # Wraps a prompt generation inside of ActiveSupport::Notifications instrumentation.
166
+ #
167
+ # This method is actually called by the +ActionPrompt::Prompt+ object itself
168
+ # through a callback when you call <tt>:generate_prompt</tt> on the +ActionPrompt::Prompt+,
169
+ # calling +generate_prompt+ directly and passing an +ActionPrompt::Prompt+ will do
170
+ # nothing except tell the logger you generated the prompt.
171
+ def generate_prompt(prompt) # :nodoc:
172
+ ActiveSupport::Notifications.instrument("deliver.active_agent") do |payload|
173
+ set_payload_for_prompt(payload, prompt)
174
+ yield # Let Prompt do the generation actions
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ def set_payload_for_prompt(payload, prompt)
181
+ payload[:prompt] = prompt.encoded
182
+ payload[:agent] = name
183
+ payload[:message_id] = prompt.message_id
184
+ payload[:subject] = prompt.subject
185
+ payload[:to] = prompt.to
186
+ payload[:from] = prompt.from
187
+ payload[:bcc] = prompt.bcc if prompt.bcc.present?
188
+ payload[:cc] = prompt.cc if prompt.cc.present?
189
+ payload[:date] = prompt.date
190
+ payload[:perform_generations] = prompt.perform_generations
191
+ end
192
+
193
+ def method_missing(method_name, ...)
194
+ if action_methods.include?(method_name.name)
195
+ Generation.new(self, method_name, ...)
196
+ else
197
+ super
198
+ end
199
+ end
200
+
201
+ def respond_to_missing?(method, include_all = false)
202
+ action_methods.include?(method.name) || super
203
+ end
204
+ end
205
+
206
+ attr_internal :context
207
+
208
+ def perform_generation
209
+ context.options.merge(options)
210
+ generation_provider.generate(context) if context && generation_provider
211
+ end
212
+
213
+ def initialize
214
+ super
215
+ @_prompt_was_called = false
216
+ @_context = ActiveAgent::ActionPrompt::Prompt.new(instructions: options[:instructions])
217
+ end
218
+
219
+ def process(method_name, *args) # :nodoc:
220
+ payload = {
221
+ agent: self.class.name,
222
+ action: method_name,
223
+ args: args
224
+ }
225
+
226
+ ActiveSupport::Notifications.instrument("process.active_agent", payload) do
227
+ super
228
+ @_context = ActiveAgent::ActionPrompt::Prompt.new unless @_prompt_was_called
229
+ end
230
+ end
231
+ ruby2_keywords(:process)
232
+
233
+ class NullPrompt # :nodoc:
234
+ def message
235
+ ""
236
+ end
237
+
238
+ def header
239
+ {}
240
+ end
241
+
242
+ def respond_to?(string, include_all = false)
243
+ true
244
+ end
245
+
246
+ def method_missing(...)
247
+ nil
248
+ end
249
+ end
250
+
251
+ # Returns the name of the agent object.
252
+ def agent_name
253
+ self.class.agent_name
254
+ end
255
+
256
+ def headers(args = nil)
257
+ if args
258
+ @_context.headers(args)
259
+ else
260
+ @_context
261
+ end
262
+ end
263
+
264
+ # def attachments
265
+ # if @_prompt_was_called
266
+ # LateAttachmentsProxy.new(@_context.attachments)
267
+ # else
268
+ # @_context.attachments
269
+ # end
270
+ # end
271
+
272
+ class LateAttachmentsProxy < SimpleDelegator
273
+ def inline
274
+ self
275
+ end
276
+
277
+ def []=(_name, _content)
278
+ _raise_error
279
+ end
280
+
281
+ private
282
+
283
+ def _raise_error
284
+ raise "Can't add attachments after `prompt` was called.\n" \
285
+ "Make sure to use `attachments[]=` before calling `prompt`."
286
+ end
287
+ end
288
+
289
+ def prompt(headers = {}, &block)
290
+ return context if @_prompt_was_called && headers.blank? && !block
291
+
292
+ content_type = headers[:content_type]
293
+
294
+ headers = apply_defaults(headers)
295
+
296
+ context.charset = charset = headers[:charset]
297
+
298
+ responses = collect_responses(headers, &block)
299
+ @_prompt_was_called = true
300
+
301
+ create_parts_from_responses(context, responses)
302
+
303
+ context.content_type = set_content_type(context, content_type, headers[:content_type])
304
+ context.charset = charset
305
+ context.actions = headers[:actions] || action_schemas
306
+ binding.irb
307
+ context
308
+ end
309
+
310
+ def action_schemas
311
+ action_methods.map do |action|
312
+ JSON.parse render_to_string(locals: {action_name: action}, action: action, formats: :json)
313
+ end
314
+ end
315
+
316
+ private
317
+
318
+ def set_content_type(m, user_content_type, class_default) # :doc:
319
+ if user_content_type.present?
320
+ user_content_type
321
+ else
322
+ context.content_type || class_default
323
+ end
324
+ end
325
+
326
+ # Translates the +subject+ using \Rails I18n class under <tt>[agent_scope, action_name]</tt> scope.
327
+ # If it does not find a translation for the +subject+ under the specified scope it will default to a
328
+ # humanized version of the <tt>action_name</tt>.
329
+ # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
330
+ def default_i18n_subject(interpolations = {}) # :doc:
331
+ agent_scope = self.class.agent_name.tr("/", ".")
332
+ I18n.t(:subject, **interpolations.merge(scope: [agent_scope, action_name], default: action_name.humanize))
333
+ end
334
+
335
+ def apply_defaults(headers)
336
+ default_values = self.class.default.except(*headers.keys).transform_values do |value|
337
+ compute_default(value)
338
+ end
339
+
340
+ headers.reverse_merge(default_values)
341
+ end
342
+
343
+ def compute_default(value)
344
+ return value unless value.is_a?(Proc)
345
+
346
+ if value.arity == 1
347
+ instance_exec(self, &value)
348
+ else
349
+ instance_exec(&value)
350
+ end
351
+ end
352
+
353
+ def assign_headers_to_context(context, headers)
354
+ assignable = headers.except(:parts_order, :content_type, :body, :template_name,
355
+ :template_path, :delivery_method, :delivery_method_options)
356
+ assignable.each { |k, v| context[k] = v }
357
+ end
358
+
359
+ def collect_responses(headers, &)
360
+ if block_given?
361
+ collect_responses_from_block(headers, &)
362
+ elsif headers[:body]
363
+ collect_responses_from_text(headers)
364
+ else
365
+ collect_responses_from_templates(headers)
366
+ end
367
+ end
368
+
369
+ def collect_responses_from_block(headers)
370
+ templates_name = headers[:template_name] || action_name
371
+ collector = ActiveAgent::ActionPrompt::Collector.new(lookup_context) { render(templates_name) }
372
+ yield(collector)
373
+ collector.responses
374
+ end
375
+
376
+ def collect_responses_from_text(headers)
377
+ [{
378
+ body: headers.delete(:body),
379
+ content_type: headers[:content_type] || "text/plain"
380
+ }]
381
+ end
382
+
383
+ def collect_responses_from_templates(headers)
384
+ templates_path = headers[:template_path] || self.class.agent_name
385
+ templates_name = headers[:template_name] || action_name
386
+
387
+ each_template(Array(templates_path), templates_name).map do |template|
388
+ format = template.format || formats.first
389
+ {
390
+ body: render(template: template, formats: [format]),
391
+ content_type: Mime[format].to_s
392
+ }
393
+ end
394
+ end
395
+
396
+ def each_template(paths, name, &)
397
+ templates = lookup_context.find_all(name, paths)
398
+ if templates.empty?
399
+ raise ActionView::MissingTemplate.new(paths, name, paths, false, "agent")
400
+ else
401
+ templates.uniq(&:format).each(&)
402
+ end
403
+ end
404
+
405
+ def create_parts_from_responses(context, responses)
406
+ if responses.size > 1 && false
407
+ prompt_container = ActiveAgent::ActionPrompt::Prompt.new
408
+ prompt_container.content_type = "multipart/alternative"
409
+ responses.each { |r| insert_part(context, r, context.charset) }
410
+ context.add_part(prompt_container)
411
+ else
412
+ responses.each { |r| insert_part(context, r, context.charset) }
413
+ end
414
+ end
415
+
416
+ def insert_part(container, response, charset)
417
+ response[:charset] ||= charset
418
+ container.add_part(response)
419
+ end
420
+
421
+ # This and #instrument_name is for caching instrument
422
+ def instrument_payload(key)
423
+ {
424
+ agent: agent_name,
425
+ key: key
426
+ }
427
+ end
428
+
429
+ def instrument_name
430
+ "active_agent"
431
+ end
432
+
433
+ def _protected_ivars
434
+ PROTECTED_IVARS
435
+ end
436
+
437
+ ActiveSupport.run_load_hooks(:active_agent, self)
438
+ end
439
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAgent
4
+ module Callbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include ActiveSupport::Callbacks
9
+ define_callbacks :generate, skip_after_callbacks_if_terminated: true
10
+ end
11
+
12
+ module ClassMethods
13
+ # Defines a callback that will get called right before the
14
+ # prompt is sent to the generation provider method.
15
+ def before_generate(*filters, &)
16
+ set_callback(:generate, :before, *filters, &)
17
+ end
18
+
19
+ # Defines a callback that will get called right after the
20
+ # prompt's generation method is finished.
21
+ def after_generate(*filters, &)
22
+ set_callback(:generate, :after, *filters, &)
23
+ end
24
+
25
+ # Defines a callback that will get called around the prompt's generation method.
26
+ def around_generate(*filters, &)
27
+ set_callback(:generate, :around, *filters, &)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAgent
4
+ def self.deprecator # :nodoc:
5
+ @deprecator ||= ActiveSupport::Deprecation.new
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # require "rails/engine"
2
+
3
+ # module ActiveAgent
4
+ # class Engine < ::Rails::Engine
5
+ # isolate_namespace ActiveAgent
6
+
7
+ # initializer "active_agent.view_paths" do |app|
8
+ # # Adding your gem's view path to Rails lookup paths
9
+ # ActiveSupport.on_load(:action_controller) do
10
+ # append_view_path Engine.root.join("app/views")
11
+ # end
12
+ # end
13
+ # end
14
+ # end
@@ -0,0 +1,78 @@
1
+ # lib/active_agent/generation.rb
2
+ require "delegate"
3
+
4
+ module ActiveAgent
5
+ class Generation < Delegator
6
+ def initialize(agent_class, action, *args)
7
+ @agent_class, @action, @args = agent_class, action, args
8
+ @processed_agent = nil
9
+ @prompt_context = nil
10
+ end
11
+ ruby2_keywords(:initialize)
12
+
13
+ def __getobj__
14
+ @prompt_context ||= processed_agent.context
15
+ end
16
+
17
+ def __setobj__(prompt_context)
18
+ @prompt_context = prompt_context
19
+ end
20
+
21
+ def context
22
+ __getobj__
23
+ end
24
+
25
+ def processed?
26
+ @processed_agent || @prompt_context
27
+ end
28
+
29
+ def generate_later!(options = {})
30
+ enqueue_generation :generate_now!, options
31
+ end
32
+
33
+ def generate_later(options = {})
34
+ enqueue_generation :generate_now, options
35
+ end
36
+
37
+ def generate_now!
38
+ processed_agent.handle_exceptions do
39
+ processed_agent.run_callbacks(:generate) do
40
+ processed_agent..perform_generation
41
+ end
42
+ end
43
+ end
44
+
45
+ def generate_now
46
+ processed_agent.handle_exceptions do
47
+ processed_agent.run_callbacks(:generate) do
48
+ processed_agent.perform_generation
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def processed_agent
56
+ @processed_agent ||= @agent_class.new.tap do |agent|
57
+ agent.process(@action, *@args)
58
+ end
59
+ end
60
+
61
+ def enqueue_generation(generation_method, options = {})
62
+ if processed?
63
+ ::Kernel.raise "You've accessed the context before asking to " \
64
+ "generate it later, so you may have made local changes that would " \
65
+ "be silently lost if we enqueued a job to generate it. Why? Only " \
66
+ "the agent method *arguments* are passed with the generation job! " \
67
+ "Do not access the context in any way if you mean to generate it " \
68
+ "later. Workarounds: 1. don't touch the context before calling " \
69
+ "#generate_later, 2. only touch the context *within your agent " \
70
+ "method*, or 3. use a custom Active Job instead of #generate_later."
71
+ else
72
+ @agent_class.generation_job.set(options).perform_later(
73
+ @agent_class.name, @action.to_s, args: @args
74
+ )
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job"
4
+
5
+ module ActiveAgent
6
+ # = Active Agent \GenerationJob
7
+ #
8
+ # The +ActiveAgent::GenerationJob+ class is used when you
9
+ # want to generate content outside of the request-response cycle. It supports
10
+ # sending messages with parameters.
11
+ #
12
+ # Exceptions are rescued and handled by the agent class.
13
+ class GenerationJob < ActiveJob::Base # :nodoc:
14
+ queue_as do
15
+ agent_class = arguments.first.constantize
16
+ agent_class.generate_later_queue_name
17
+ end
18
+
19
+ rescue_from StandardError, with: :handle_exception_with_agent_class
20
+
21
+ def perform(agent_class_name, action_name, args:, params: nil)
22
+ agent_class = agent_class_name.constantize
23
+ agent = agent_class.new
24
+ agent.params = params if params
25
+ agent.process(action_name, *args)
26
+ agent.generate
27
+ end
28
+
29
+ private
30
+
31
+ # "Deserialize" the agent class name by hand in case another argument
32
+ # (like a Global ID reference) raised DeserializationError.
33
+ def agent_class
34
+ if agent = Array(@serialized_arguments).first || Array(arguments).first
35
+ agent.constantize
36
+ end
37
+ end
38
+
39
+ def handle_exception_with_agent_class(exception)
40
+ if klass = agent_class
41
+ klass.handle_exception exception
42
+ else
43
+ raise exception
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require_relative "action_prompt"
5
+
6
+ module ActiveAgent
7
+ # = Active Agent \GenerationM74ethods
8
+ #
9
+ # This module handles everything related to prompt generation, from registering
10
+ # new generation methods to configuring the prompt object to be sent.
11
+ module GenerationMethods
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ # Do not make this inheritable, because we always want it to propagate
16
+ cattr_accessor :raise_generation_errors, default: true
17
+ cattr_accessor :perform_generations, default: true
18
+
19
+ class_attribute :generation_methods, default: {}.freeze
20
+ class_attribute :generation_method, default: :smtp
21
+
22
+ add_generation_method :test, ActiveAgent::ActionPrompt::TestAgent
23
+ end
24
+
25
+ module ClassMethods
26
+ delegate :generations, :generations=, to: ActiveAgent::ActionPrompt::TestAgent
27
+
28
+ def add_generation_method(symbol, klass, default_options = {})
29
+ class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
30
+ public_send(:"#{symbol}_settings=", default_options)
31
+ self.generation_methods = generation_methods.merge(symbol.to_sym => klass).freeze
32
+ end
33
+
34
+ def wrap_generation_behavior(prompt, method = nil, options = nil) # :nodoc:
35
+ method ||= generation_method
36
+ prompt.generation_handler = self
37
+
38
+ case method
39
+ when NilClass
40
+ raise "Generation method cannot be nil"
41
+ when Symbol
42
+ if klass = generation_methods[method]
43
+ prompt.generation_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
44
+ else
45
+ raise "Invalid generation method #{method.inspect}"
46
+ end
47
+ else
48
+ prompt.generation_method(method)
49
+ end
50
+
51
+ prompt.perform_generations = perform_generations
52
+ prompt.raise_generation_errors = raise_generation_errors
53
+ end
54
+ end
55
+
56
+ def wrap_generation_behavior!(*) # :nodoc:
57
+ self.class.wrap_generation_behavior(prompt, *)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ # Active Agent: Generation Provider
2
+
3
+ This README provides information about the base generation provider class and the generation provider module interfaces in the ActiveAgent library.
4
+
5
+ ## Main Components
6
+
7
+ Base class - for creating and configuring generation providers.
8
+ Module - for including in your agent classes to provide generation provider functionality.
9
+
10
+ ### ActiveAgent::GenerationProvider::Base
11
+
12
+ The main class for creating and configuring generation providers. It provides the core functionality for creating and managing generation providers.
13
+
14
+ #### Core Methods
15
+
16
+ ##### initialize(options = {})
17
+ Creates a new generation provider object with the given options.