activeagent 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.