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.
- 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 +63 -0
- 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 +129 -4
@@ -0,0 +1,36 @@
|
|
1
|
+
# lib/active_agent/generation_provider/base.rb
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module GenerationProvider
|
5
|
+
class Base
|
6
|
+
class GenerationProviderError < StandardError; end
|
7
|
+
attr_reader :client, :config, :prompt
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
@prompt = nil
|
12
|
+
@response = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate(prompt)
|
16
|
+
raise NotImplementedError, "Subclasses must implement the 'generate' method"
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def handle_response(response)
|
22
|
+
ActiveAgent::GenerationProvider::Response.new(message:, raw_response: response)
|
23
|
+
raise NotImplementedError, "Subclasses must implement the 'handle_response' method"
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def prompt_parameters
|
29
|
+
{
|
30
|
+
messages: @prompt.messages,
|
31
|
+
temperature: @config["temperature"] || 0.7
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# lib/active_agent/generation_provider/open_ai_provider.rb
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "openai"
|
5
|
+
require "active_agent/generation_provider/response"
|
6
|
+
|
7
|
+
module ActiveAgent
|
8
|
+
module GenerationProvider
|
9
|
+
class OpenAIProvider < Base
|
10
|
+
def initialize(config)
|
11
|
+
super
|
12
|
+
@api_key = config["api_key"]
|
13
|
+
@model_name = config["model"] || "gpt-4o-mini"
|
14
|
+
@client = OpenAI::Client.new(api_key: @api_key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate(prompt)
|
18
|
+
@prompt = prompt
|
19
|
+
parameters = prompt_parameters.merge(model: @model_name)
|
20
|
+
|
21
|
+
# parameters[:instructions] = prompt.instructions.content if prompt.instructions.present?
|
22
|
+
|
23
|
+
parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
|
24
|
+
|
25
|
+
response = @client.chat(parameters: parameters)
|
26
|
+
handle_response(response)
|
27
|
+
rescue => e
|
28
|
+
raise GenerationProviderError, e.message
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def provider_stream
|
34
|
+
# prompt.config[:stream] will define a proc found in prompt at runtime
|
35
|
+
# config[:stream] will define a proc found in config stream would come from an Agent class's generate_with or stream_with method calls
|
36
|
+
agent_stream = prompt.config[:stream] || config["stream"]
|
37
|
+
proc do |chunk, bytesize|
|
38
|
+
# Provider parsing logic here
|
39
|
+
new_content = chunk.dig("choices", 0, "delta", "content")
|
40
|
+
message = @prompt.messages.find { |message| message.response_number == chunk.dig("choices", 0, "index") }
|
41
|
+
message.update(content: message.content + new_content) if new_content
|
42
|
+
|
43
|
+
# Call the custom stream_proc if provided
|
44
|
+
agent_stream.call(message) if agent_stream.respond_to?(:call)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def prompt_parameters
|
49
|
+
{
|
50
|
+
messages: @prompt.messages,
|
51
|
+
temperature: @config["temperature"] || 0.7,
|
52
|
+
tools: @prompt.actions
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_response(response)
|
57
|
+
message_json = response.dig("choices", 0, "message")
|
58
|
+
message = ActiveAgent::ActionPrompt::Message.new(
|
59
|
+
content: message_json["content"],
|
60
|
+
role: message_json["role"],
|
61
|
+
action_reqested: message_json["function_call"],
|
62
|
+
requested_actions: message_json["tool_calls"]
|
63
|
+
)
|
64
|
+
ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module GenerationProvider
|
5
|
+
class Response
|
6
|
+
attr_reader :message, :prompt, :raw_response
|
7
|
+
|
8
|
+
def initialize(prompt:, message:, raw_response: nil)
|
9
|
+
@message = message
|
10
|
+
@prompt = prompt
|
11
|
+
@raw_response = raw_response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module GenerationProvider
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :_generation_provider_name, instance_accessor: false, instance_predicate: false
|
9
|
+
class_attribute :_generation_provider, instance_accessor: false, instance_predicate: false
|
10
|
+
|
11
|
+
delegate :generation_provider, to: :class
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def configuration(provider_name, **options)
|
16
|
+
config = ActiveAgent.config[provider_name.to_s] || ActiveAgent.config[ENV["RAILS_ENV"]][provider_name.to_s]
|
17
|
+
|
18
|
+
raise "Configuration not found for provider: #{provider_name}" unless config
|
19
|
+
|
20
|
+
config.merge!(options)
|
21
|
+
configure_provider(config)
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure_provider(config)
|
25
|
+
require "active_agent/generation_provider/#{config["service"].underscore}_provider"
|
26
|
+
ActiveAgent::GenerationProvider.const_get("#{config["service"].camelize}Provider").new(config)
|
27
|
+
rescue LoadError
|
28
|
+
raise "Missing generation provider for #{config["service"].inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def generation_provider
|
32
|
+
self.generation_provider = :openai if _generation_provider.nil?
|
33
|
+
_generation_provider
|
34
|
+
end
|
35
|
+
|
36
|
+
def generation_provider_name
|
37
|
+
self.generation_provider = :openai if _generation_provider_name.nil?
|
38
|
+
_generation_provider_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def generation_provider=(name_or_provider)
|
42
|
+
case name_or_provider
|
43
|
+
when Symbol, String
|
44
|
+
provider = configuration(name_or_provider)
|
45
|
+
assign_provider(name_or_provider.to_s, provider)
|
46
|
+
else
|
47
|
+
raise ArgumentError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def assign_provider(provider_name, generation_provider)
|
54
|
+
self._generation_provider_name = provider_name
|
55
|
+
self._generation_provider = generation_provider
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def generation_provider
|
60
|
+
self.class.generation_provider
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module ActiveAgent
|
6
|
+
# = Active Agent \InlinePreviewInterceptor
|
7
|
+
#
|
8
|
+
# Implements a agent preview interceptor that converts image tag src attributes
|
9
|
+
# that use inline cid: style URLs to data: style URLs so that they are visible
|
10
|
+
# when previewing an HTML prompt in a web browser.
|
11
|
+
#
|
12
|
+
# This interceptor is enabled by default. To disable it, delete it from the
|
13
|
+
# <tt>ActiveAgent::Base.preview_interceptors</tt> array:
|
14
|
+
#
|
15
|
+
# ActiveAgent::Base.preview_interceptors.delete(ActiveAgent::InlinePreviewInterceptor)
|
16
|
+
#
|
17
|
+
class InlinePreviewInterceptor
|
18
|
+
PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i
|
19
|
+
|
20
|
+
include Base64
|
21
|
+
|
22
|
+
def self.previewing_prompt(context) # :nodoc:
|
23
|
+
new(context).transform!
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(context) # :nodoc:
|
27
|
+
@context = context
|
28
|
+
end
|
29
|
+
|
30
|
+
def transform! # :nodoc:
|
31
|
+
return context if html_part.blank?
|
32
|
+
|
33
|
+
html_part.body = html_part.decoded.gsub(PATTERN) do |match|
|
34
|
+
if part = find_part(match[9..-2])
|
35
|
+
%(src="#{data_url(part)}")
|
36
|
+
else
|
37
|
+
match
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :context
|
47
|
+
|
48
|
+
def html_part
|
49
|
+
@html_part ||= context.html_part
|
50
|
+
end
|
51
|
+
|
52
|
+
def data_url(part)
|
53
|
+
"data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_part(cid)
|
57
|
+
context.all_parts.find { |p| p.attachment? && p.cid == cid }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
|
3
|
+
# require "active_support/log_subscriber"
|
4
|
+
|
5
|
+
# module ActiveAgent
|
6
|
+
# # = Active Agent \LogSubscriber
|
7
|
+
# #
|
8
|
+
# # Implements the ActiveSupport::LogSubscriber for logging notifications when
|
9
|
+
# # prompt is generated.
|
10
|
+
# class LogSubscriber < ActiveSupport::LogSubscriber
|
11
|
+
# # A prompt was generated.
|
12
|
+
# def deliver(event)
|
13
|
+
# info do
|
14
|
+
# if exception = event.payload[:exception_object]
|
15
|
+
# "Failed delivery of prompt #{event.payload[:message_id]} error_class=#{exception.class} error_message=#{exception.message.inspect}"
|
16
|
+
# elsif event.payload[:perform_deliveries]
|
17
|
+
# "Generated response for prompt #{event.payload[:message_id]} (#{event.duration.round(1)}ms)"
|
18
|
+
# else
|
19
|
+
# "Skipped generation of prompt #{event.payload[:message_id]} as `perform_generation` is false"
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
|
23
|
+
# debug { event.payload[:mail] }
|
24
|
+
# end
|
25
|
+
# subscribe_log_level :deliver, :debug
|
26
|
+
|
27
|
+
# # An email was generated.
|
28
|
+
# def process(event)
|
29
|
+
# debug do
|
30
|
+
# agent = event.payload[:agent]
|
31
|
+
# action = event.payload[:action]
|
32
|
+
# "#{agent}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# subscribe_log_level :process, :debug
|
36
|
+
|
37
|
+
# # Use the logger configured for ActionMailer::Base.
|
38
|
+
# def logger
|
39
|
+
# ActionMailer::Base.logger
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
|
44
|
+
# ActionMailer::LogSubscriber.attach_to :action_mailer
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActiveAgent
|
2
|
+
class Operation < AbstractController::Base
|
3
|
+
include AbstractController::Rendering
|
4
|
+
include ActionView::Rendering # Allows rendering of ERB templates without a view context tied to a request
|
5
|
+
append_view_path 'app/views' # Ensure the controller knows where to look for view templates
|
6
|
+
|
7
|
+
def process_tool(tool_name, params)
|
8
|
+
send(tool_name, params) # Dynamically calls the method corresponding to tool_name
|
9
|
+
rescue NoMethodError
|
10
|
+
"Tool not found: #{tool_name}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module Parameterized
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attr_writer :params
|
9
|
+
|
10
|
+
def params
|
11
|
+
@params ||= {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def with(params)
|
17
|
+
ActiveAgent::Parameterized::Agent.new(self, params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Agent
|
22
|
+
def initialize(agent, params)
|
23
|
+
@agent = agent
|
24
|
+
@params = params
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, ...)
|
28
|
+
if @agent.public_instance_methods.include?(method_name)
|
29
|
+
ActiveAgent::Parameterized::Generation.new(@agent, method_name, @params, ...)
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_to_missing?(method, include_all = false)
|
36
|
+
@agent.respond_to?(method, include_all)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Generation < ActiveAgent::Generation
|
41
|
+
def initialize(agent_class, action, params, ...)
|
42
|
+
super(agent_class, action, ...)
|
43
|
+
@params = params
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def processed_agent
|
49
|
+
@processed_agent ||= @agent_class.new.tap do |agent|
|
50
|
+
agent.params = @params
|
51
|
+
agent.process @action, *@args
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def enqueue_generation(generation_method, options = {})
|
56
|
+
if processed?
|
57
|
+
super
|
58
|
+
else
|
59
|
+
@agent_class.generation_job.set(options).perform_later(
|
60
|
+
@agent_class.name, @action.to_s, params: @params, args: @args
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/descendants_tracker"
|
4
|
+
|
5
|
+
module ActiveAgent
|
6
|
+
module Previews # :nodoc:
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
mattr_accessor :preview_paths, instance_writer: false, default: []
|
11
|
+
|
12
|
+
mattr_accessor :show_previews, instance_writer: false
|
13
|
+
|
14
|
+
mattr_accessor :preview_interceptors, instance_writer: false, default: [ActiveAgent::InlinePreviewInterceptor]
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Register one or more Interceptors which will be called before prompt is previewed.
|
19
|
+
def register_preview_interceptors(*interceptors)
|
20
|
+
interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Unregister one or more previously registered Interceptors.
|
24
|
+
def unregister_preview_interceptors(*interceptors)
|
25
|
+
interceptors.flatten.compact.each { |interceptor| unregister_preview_interceptor(interceptor) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Register an Interceptor which will be called before prompt is previewed.
|
29
|
+
# Either a class or a string can be passed in as the Interceptor. If a
|
30
|
+
# string is passed in it will be constantized.
|
31
|
+
def register_preview_interceptor(interceptor)
|
32
|
+
preview_interceptor = interceptor_class_for(interceptor)
|
33
|
+
|
34
|
+
unless preview_interceptors.include?(preview_interceptor)
|
35
|
+
preview_interceptors << preview_interceptor
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Unregister a previously registered Interceptor.
|
40
|
+
# Either a class or a string can be passed in as the Interceptor. If a
|
41
|
+
# string is passed in it will be constantized.
|
42
|
+
def unregister_preview_interceptor(interceptor)
|
43
|
+
preview_interceptors.delete(interceptor_class_for(interceptor))
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def interceptor_class_for(interceptor)
|
49
|
+
case interceptor
|
50
|
+
when String, Symbol
|
51
|
+
interceptor.to_s.camelize.constantize
|
52
|
+
else
|
53
|
+
interceptor
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Preview
|
60
|
+
extend ActiveSupport::DescendantsTracker
|
61
|
+
|
62
|
+
attr_reader :params
|
63
|
+
|
64
|
+
def initialize(params = {})
|
65
|
+
@params = params
|
66
|
+
end
|
67
|
+
|
68
|
+
class << self
|
69
|
+
# Returns all agent preview classes.
|
70
|
+
def all
|
71
|
+
load_previews if descendants.empty?
|
72
|
+
descendants.sort_by { |agent| agent.name.titleize }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the prompt object for the given context. The registered preview
|
76
|
+
# interceptors will be informed so that they can transform the message
|
77
|
+
# as they would if the mail was actually being delivered.
|
78
|
+
def call(context, params = {})
|
79
|
+
preview = new(params)
|
80
|
+
prompt = preview.public_send(context)
|
81
|
+
inform_preview_interceptors(prompt)
|
82
|
+
prompt
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns all of the available prompt previews.
|
86
|
+
def prompts
|
87
|
+
public_instance_methods(false).map(&:to_s).sort
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns +true+ if the prompt exists.
|
91
|
+
def prompt_exists?(prompt)
|
92
|
+
prompts.include?(prompt)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns +true+ if the preview exists.
|
96
|
+
def exists?(preview)
|
97
|
+
all.any? { |p| p.preview_name == preview }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Find a agent preview by its underscored class name.
|
101
|
+
def find(preview)
|
102
|
+
all.find { |p| p.preview_name == preview }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the underscored name of the agent preview without the suffix.
|
106
|
+
def preview_name
|
107
|
+
name.delete_suffix("Preview").underscore
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def load_previews
|
113
|
+
preview_paths.each do |preview_path|
|
114
|
+
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require file }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def preview_paths
|
119
|
+
Base.preview_paths
|
120
|
+
end
|
121
|
+
|
122
|
+
def show_previews
|
123
|
+
Base.show_previews
|
124
|
+
end
|
125
|
+
|
126
|
+
def inform_preview_interceptors(context)
|
127
|
+
Base.preview_interceptors.each do |interceptor|
|
128
|
+
interceptor.previewing_prompt(context)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
# = Active Agent's Action Prompt \PromptHelper
|
5
|
+
#
|
6
|
+
# Provides helper methods for ActiveAgent::Base that can be used for easily
|
7
|
+
# formatting prompts, accessing agent or prompt instances.
|
8
|
+
module PromptHelper
|
9
|
+
# Access the agent instance.
|
10
|
+
def agent
|
11
|
+
@_controller
|
12
|
+
end
|
13
|
+
|
14
|
+
# Access the prompt instance.
|
15
|
+
def context
|
16
|
+
@_context
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
module QueuedGeneration
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :generation_job, default: ::ActiveAgent::GenerationJob
|
9
|
+
class_attribute :generate_later_queue_name, default: :agents
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_job/railtie"
|
4
|
+
require "active_agent"
|
5
|
+
require "active_agent/engine"
|
6
|
+
require "rails"
|
7
|
+
require "abstract_controller/railties/routes_helpers"
|
8
|
+
|
9
|
+
module ActiveAgent
|
10
|
+
class Railtie < Rails::Railtie # :nodoc:
|
11
|
+
config.active_agent = ActiveSupport::OrderedOptions.new
|
12
|
+
config.active_agent.preview_paths = []
|
13
|
+
config.eager_load_namespaces << ::ActiveAgent
|
14
|
+
|
15
|
+
initializer "active_agent.deprecator", before: :load_environment_config do |app|
|
16
|
+
app.deprecators[:active_agent] = ActiveAgent.deprecator
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer "active_agent.logger" do
|
20
|
+
ActiveSupport.on_load(:active_agent) { self.logger ||= Rails.logger }
|
21
|
+
end
|
22
|
+
|
23
|
+
initializer "active_agent.set_configs" do |app|
|
24
|
+
paths = app.config.paths
|
25
|
+
options = app.config.active_agent
|
26
|
+
|
27
|
+
options.assets_dir ||= paths["public"].first
|
28
|
+
options.javascripts_dir ||= paths["public/javascripts"].first
|
29
|
+
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
30
|
+
options.show_previews = Rails.env.development? if options.show_previews.nil?
|
31
|
+
options.cache_store ||= Rails.cache
|
32
|
+
options.preview_paths |= ["#{Rails.root}/test/agents/previews"]
|
33
|
+
|
34
|
+
# make sure readers methods get compiled
|
35
|
+
options.asset_host ||= app.config.asset_host
|
36
|
+
options.relative_url_root ||= app.config.relative_url_root
|
37
|
+
|
38
|
+
ActiveSupport.on_load(:active_agent) do
|
39
|
+
include AbstractController::UrlFor
|
40
|
+
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
|
41
|
+
include app.routes.mounted_helpers
|
42
|
+
|
43
|
+
register_interceptors(options.delete(:interceptors))
|
44
|
+
register_preview_interceptors(options.delete(:preview_interceptors))
|
45
|
+
register_observers(options.delete(:observers))
|
46
|
+
self.view_paths = ["#{Rails.root}/app/views"]
|
47
|
+
self.preview_paths |= options[:preview_paths]
|
48
|
+
|
49
|
+
if delivery_job = options.delete(:delivery_job)
|
50
|
+
self.delivery_job = delivery_job.constantize
|
51
|
+
end
|
52
|
+
|
53
|
+
if options.smtp_settings
|
54
|
+
self.smtp_settings = options.smtp_settings
|
55
|
+
end
|
56
|
+
|
57
|
+
options.each { |k, v| send(:"#{k}=", v) }
|
58
|
+
end
|
59
|
+
|
60
|
+
ActiveSupport.on_load(:action_dispatch_integration_test) do
|
61
|
+
include ActiveAgent::TestHelper
|
62
|
+
include ActiveAgent::TestCase::ClearTestDeliveries
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
initializer "active_agent.set_autoload_paths", before: :set_autoload_paths do |app|
|
67
|
+
options = app.config.active_agent
|
68
|
+
# app.config.paths["test/agents/previews"].concat(options.preview_paths)
|
69
|
+
end
|
70
|
+
|
71
|
+
initializer "active_agent.compile_config_methods" do
|
72
|
+
ActiveSupport.on_load(:active_agent) do
|
73
|
+
config.compile_methods! if config.respond_to?(:compile_methods!)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
config.after_initialize do |app|
|
78
|
+
options = app.config.active_agent
|
79
|
+
|
80
|
+
if options.show_previews
|
81
|
+
app.routes.prepend do
|
82
|
+
get "/rails/agents" => "rails/agents#index", :internal => true
|
83
|
+
get "/rails/agents/download/*path" => "rails/agents#download", :internal => true
|
84
|
+
get "/rails/agents/*path" => "rails/agents#preview", :internal => true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent # :nodoc:
|
4
|
+
# = Active Agent \Rescuable
|
5
|
+
#
|
6
|
+
# Provides
|
7
|
+
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
|
8
|
+
# for agents. Wraps agent action processing, generation job processing, and prompt
|
9
|
+
# generation to handle configured errors.
|
10
|
+
module Rescuable
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
include ActiveSupport::Rescuable
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def handle_exception(exception) # :nodoc:
|
16
|
+
rescue_with_handler(exception) || raise(exception)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def handle_exceptions # :nodoc:
|
21
|
+
yield
|
22
|
+
rescue => exception
|
23
|
+
rescue_with_handler(exception) || raise
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def process(...)
|
29
|
+
handle_exceptions do
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveAgent
|
4
|
+
class Service
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
autoload :Configurator
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def configure(service_name, configurations)
|
11
|
+
Configurator.build(service_name, configurations)
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(configurator:, name:, service: nil, **service_config) # :nodoc:
|
15
|
+
new(**service_config).tap do |service_instance|
|
16
|
+
service_instance.name = name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate(...)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|