activeagent 0.0.1.alpha → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +153 -0
- data/Rakefile +3 -0
- data/lib/active_agent/README.md +21 -0
- data/lib/active_agent/action_prompt/README.md +44 -0
- data/lib/active_agent/action_prompt/base.rb +0 -0
- data/lib/active_agent/action_prompt/collector.rb +34 -0
- data/lib/active_agent/action_prompt/message.rb +44 -0
- data/lib/active_agent/action_prompt/prompt.rb +79 -0
- data/lib/active_agent/action_prompt.rb +127 -0
- data/lib/active_agent/base.rb +439 -0
- data/lib/active_agent/callbacks.rb +31 -0
- data/lib/active_agent/deprecator.rb +7 -0
- data/lib/active_agent/engine.rb +14 -0
- data/lib/active_agent/generation.rb +78 -0
- data/lib/active_agent/generation_job.rb +47 -0
- data/lib/active_agent/generation_methods.rb +60 -0
- data/lib/active_agent/generation_provider/README.md +17 -0
- data/lib/active_agent/generation_provider/base.rb +36 -0
- data/lib/active_agent/generation_provider/open_ai_provider.rb +68 -0
- data/lib/active_agent/generation_provider/response.rb +15 -0
- data/lib/active_agent/generation_provider.rb +63 -0
- data/lib/active_agent/inline_preview_interceptor.rb +60 -0
- data/lib/active_agent/log_subscriber.rb +44 -0
- data/lib/active_agent/operation.rb +13 -0
- data/lib/active_agent/parameterized.rb +66 -0
- data/lib/active_agent/preview.rb +133 -0
- data/lib/active_agent/prompt_helper.rb +19 -0
- data/lib/active_agent/queued_generation.rb +12 -0
- data/lib/active_agent/railtie.rb +89 -0
- data/lib/active_agent/rescuable.rb +34 -0
- data/lib/active_agent/service.rb +25 -0
- data/lib/active_agent/test_case.rb +125 -0
- data/lib/active_agent/version.rb +3 -0
- data/lib/active_agent.rb +27 -5
- data/lib/generators/active_agent/USAGE +18 -0
- data/lib/generators/active_agent/agent_generator.rb +63 -0
- data/lib/generators/active_agent/templates/action.html.erb.tt +0 -0
- data/lib/generators/active_agent/templates/action.json.jbuilder.tt +33 -0
- data/lib/generators/active_agent/templates/agent.rb.tt +14 -0
- data/lib/generators/active_agent/templates/agent_spec.rb.tt +0 -0
- data/lib/generators/active_agent/templates/agent_test.rb.tt +0 -0
- data/lib/generators/active_agent/templates/application_agent.rb.tt +4 -0
- metadata +128 -3
@@ -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,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.
|