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,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