activeagent 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +153 -0
  3. data/Rakefile +3 -0
  4. data/lib/active_agent/README.md +21 -0
  5. data/lib/active_agent/action_prompt/README.md +44 -0
  6. data/lib/active_agent/action_prompt/base.rb +0 -0
  7. data/lib/active_agent/action_prompt/collector.rb +34 -0
  8. data/lib/active_agent/action_prompt/message.rb +44 -0
  9. data/lib/active_agent/action_prompt/prompt.rb +79 -0
  10. data/lib/active_agent/action_prompt.rb +127 -0
  11. data/lib/active_agent/base.rb +439 -0
  12. data/lib/active_agent/callbacks.rb +31 -0
  13. data/lib/active_agent/deprecator.rb +7 -0
  14. data/lib/active_agent/engine.rb +14 -0
  15. data/lib/active_agent/generation.rb +78 -0
  16. data/lib/active_agent/generation_job.rb +47 -0
  17. data/lib/active_agent/generation_methods.rb +60 -0
  18. data/lib/active_agent/generation_provider/README.md +17 -0
  19. data/lib/active_agent/generation_provider/base.rb +36 -0
  20. data/lib/active_agent/generation_provider/open_ai_provider.rb +68 -0
  21. data/lib/active_agent/generation_provider/response.rb +15 -0
  22. data/lib/active_agent/generation_provider.rb +63 -0
  23. data/lib/active_agent/inline_preview_interceptor.rb +60 -0
  24. data/lib/active_agent/log_subscriber.rb +44 -0
  25. data/lib/active_agent/operation.rb +13 -0
  26. data/lib/active_agent/parameterized.rb +66 -0
  27. data/lib/active_agent/preview.rb +133 -0
  28. data/lib/active_agent/prompt_helper.rb +19 -0
  29. data/lib/active_agent/queued_generation.rb +12 -0
  30. data/lib/active_agent/railtie.rb +89 -0
  31. data/lib/active_agent/rescuable.rb +34 -0
  32. data/lib/active_agent/service.rb +25 -0
  33. data/lib/active_agent/test_case.rb +125 -0
  34. data/lib/active_agent/version.rb +3 -0
  35. data/lib/active_agent.rb +63 -0
  36. data/lib/generators/active_agent/USAGE +18 -0
  37. data/lib/generators/active_agent/agent_generator.rb +63 -0
  38. data/lib/generators/active_agent/templates/action.html.erb.tt +0 -0
  39. data/lib/generators/active_agent/templates/action.json.jbuilder.tt +33 -0
  40. data/lib/generators/active_agent/templates/agent.rb.tt +14 -0
  41. data/lib/generators/active_agent/templates/agent_spec.rb.tt +0 -0
  42. data/lib/generators/active_agent/templates/agent_test.rb.tt +0 -0
  43. data/lib/generators/active_agent/templates/application_agent.rb.tt +4 -0
  44. metadata +129 -4
@@ -0,0 +1,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