activeagent 0.1.1 → 0.2.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67bb51f0a98d5b2291b53e30b629adae069a3f24e19edfc74702a918bc3c1086
4
- data.tar.gz: 04cb6ba122dac00b145c3baf1f54157d5e443470639d16f22d614bfaaba92b49
3
+ metadata.gz: 2266e9f249f3a6ff07fb82c112714da11159d997dd802ccd2f2b1db13e2edec2
4
+ data.tar.gz: 6f2ffbe338a1b299cfc361f47a9d41bcc58f37a176d12f45b79635117efb8c8e
5
5
  SHA512:
6
- metadata.gz: 9835738ac1bb5570c900dd9a9942591a28c8b17b6ce26afbd622f764d5bb4ee178d0fc1089824a84d924824cc0c9a291680b1a1b1df5e41f5bfff1e91700a382
7
- data.tar.gz: 78e3fd95e02a950cd2c47bf52891156d861f926374333efaeee51578fc0c96f65a94a123ec104cd0a5c32c9ea130ac85075a9904478191904936ea80381df714
6
+ metadata.gz: 0e8f31ab7602dd54ada6ec8c0f809f935deff945e3ff7a603cf1c616a2d63988f742b7ea2cf2e11d690b47b05b69b1eb754b12723217a9dc34f6a80b369cd2c3
7
+ data.tar.gz: 9ce42b23f522d0c96519bbbe4888d7ff75e858c1b652cf1a4a8557997ad9c6cdbc3e38e2fe4ac2e711969c4754e289ac5da63489b84f8d243f440d2ef4b4d838
data/README.md CHANGED
@@ -1,153 +1,89 @@
1
- # ActiveAgent
1
+ # Active Agent: README.md
2
2
 
3
- ActiveAgent is a Rails framework for creating and managing AI agents. It provides a structured way to interact with AI services through agents that can generate text, images, speech-to-text, and text-to-speech. It includes modules for defining prompts, actions, and rendering generative UI, as well as scaling with asynchronous jobs and streaming.
3
+ # Active Agent
4
4
 
5
- ## Installation
5
+ ## Agent
6
6
 
7
- Add this line to your application's Gemfile:
7
+ Create agents that take instructions, prompts, and perform actions
8
8
 
9
- ```ruby
10
- gem 'active_agent'
11
- ```
12
-
13
- And then execute:
9
+ ### Generation Provider
14
10
 
15
- ```sh
16
- bundle install
11
+ ```ruby
12
+ class SupportAgent < ActiveAgent::Base
13
+ generate_with :openai, model: ‘gpt-o3-mini’, temperature: 0.7
14
+ end
17
15
  ```
18
- ## Getting Started
19
16
 
20
- ## Usage
17
+ `generate_with` sets the generation provider’s completion generation model and parameters.
21
18
 
22
- ### Generating an Agent
23
- ```
24
- rails generate agent inventory search
25
- ```
19
+ `completion_response = SupportAgent.prompt(‘Help me!’).generate_now`
26
20
 
27
- This will generate the following files:
28
- ```
29
- app/agents/application_agent.rb
30
- app/agents/inventory_agent.rb
31
- app/views/inventory_agent/search.text.erb
32
- app/views/inventory_agent/search.json.jbuilder
21
+ ```ruby
22
+ class SupportAgent < ActiveAgent::Base
23
+ generate_with :openai, model: ‘gpt-o3-mini’, temperature: 0.7
24
+ embed_with :openai, model: ‘text-embedding-3-small’
25
+ end
33
26
  ```
34
27
 
35
- ### Define Agents
28
+ `embed_with` sets the generation provider’s embedding generation model and parameters.
36
29
 
37
- Agents are the core of ActiveAgent. An agent takes prompts and can perform actions to generate content. Agents are defined by a simple Ruby class that inherits from `ActiveAgent::Base` and are located in the `app/agents` directory.
30
+ `embedding_response = SupportAgent.prompt(‘Help me!’).embed_now`
38
31
 
39
- ```ruby
40
- #
32
+ ### Instructions
41
33
 
42
- inventory_agent.rb
34
+ Instructions are system prompts that predefine the agent’s intention.
43
35
 
36
+ ### Prompt
44
37
 
45
- class InventoryAgent < ActiveAgent::Base
46
- generate_with :openai, model: 'gpt-4o-mini', temperature: 0.5, instructions: :inventory_operations
38
+ Action Prompt allows Active Agents to render plain text and HTML prompt templates. Calling generate on a prompt will send the prompt to the agent’s Generation Provider.
47
39
 
48
- def search
49
- @items = Item.search(params[:query])
50
- end
40
+ `SupportAgent.prompt(“What does CRUD and REST mean?”)`
51
41
 
52
- def inventory_operations
53
- @organization = Organization.find(params[:account_id])
54
- prompt
55
- end
56
- end
57
- ```
42
+ ### Queue Generation
58
43
 
59
- ### Interact with AI Services
44
+ Active Agent also supports queued generation with Active Job using a common Generation Job interface.
60
45
 
61
- ActiveAgent allows you to interact with various AI services to generate text, images, speech-to-text, and text-to-speech.
46
+ ### Perform actions
62
47
 
63
- ```ruby
64
- class SupportAgent < ActiveAgent::Base
65
- generate_with :openai, model: 'gpt-4o-mini', instructions: :instructions
48
+ Active Agents can define methods that are autoloaded as callable tools. These actions’ default schema will be provided to the agent’s context as part of the prompt request to the Generation Provider.
66
49
 
67
- def perform(content, context)
68
- @content = content
69
- @context = context
70
- end
50
+ ## Actions
71
51
 
72
- def generate_message
73
- provider_instance.generate(self)
74
- end
52
+ ```
53
+ def get_cat_image_base64
54
+ uri = URI("https://cataas.com/cat")
55
+ response = Net::HTTP.get_response(uri)
75
56
 
76
- private
77
-
78
- def after_generate
79
- broadcast_message
80
- end
81
-
82
- def broadcast_message
83
- broadcast_append_later_to(
84
- broadcast_stream,
85
- target: broadcast_target,
86
- partial: 'support_agent/message',
87
- locals: { message: @message }
88
- )
89
- end
90
-
91
- def broadcast_stream
92
- "#{dom_id(@chat)}_messages"
93
- end
57
+ if response.is_a?(Net::HTTPSuccess)
58
+ image_data = response.body
59
+ Base64.strict_encode64(image_data)
60
+ else
61
+ raise "Failed to fetch cat image. Status code: #{response.code}"
62
+ end
94
63
  end
95
- ```
96
-
97
- ### Render Generative UI
98
-
99
- ActiveAgent uses Action Prompt both for rendering `instructions` prompt views as well as rendering action views. Prompts are Action Views that provide instructions for the agent to generate content.
100
64
 
101
- ```erb
102
- <!--
65
+ class SupportAgent < ActiveAgent
66
+ generate_with :openai,
67
+ model: "gpt-4o",
68
+ instructions: "Help people with their problems",
69
+ temperature: 0.7
103
70
 
104
- instructions.text.erb
105
-
106
- -->
107
- INSTRUCTIONS: You are an inventory manager for <%= @organization.name %>. You can search for inventory or reconcile inventory using <%= assigned_actions %>
108
- ```
109
-
110
- ### Scale with Asynchronous Jobs and Streaming
111
-
112
- ActiveAgent supports asynchronous job processing and streaming for scalable AI interactions.
113
-
114
- #### Asynchronous Jobs
115
-
116
- Use the `generate_later` method to enqueue a job for later processing.
117
-
118
- ```ruby
119
- InventoryAgent.with(query: query).search.generate_later
71
+ def get_cat_image
72
+ prompt { |format| format.text { render plain: get_cat_image_base64 } }
73
+ end
74
+ end
120
75
  ```
121
76
 
122
- #### Streaming
123
-
124
- Use the `stream_with` method to handle streaming responses.
125
-
126
- ```ruby
127
- class InventoryAgent < ActiveAgent::Base
128
- generate_with :openai, model: 'gpt-4o-mini', stream: :broadcast_results
77
+ ## Prompts
129
78
 
130
- private
79
+ ### Basic
131
80
 
132
- def broadcast_results
133
- proc do |chunk, _bytesize|
134
- @message.content = @message.content + chunk
135
- broadcast_append_to(
136
- "#{dom_id(chat)}_messages",
137
- partial: "messages/message",
138
- locals: { message: @message, scroll_to: true },
139
- target: "#{dom_id(chat)}_messages"
140
- )
141
- end
142
- end
143
- end
144
- ```
81
+ #### Plain text prompt and response templates
145
82
 
146
- ## Contributing
83
+ ### HTML
147
84
 
148
- Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/active_agent.
85
+ ### Action Schema JSON
149
86
 
150
- ## License
87
+ response = SupportAgent.prompt(‘show me a picture of a cat’).generate_now
151
88
 
152
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
153
- ```
89
+ response.message
data/Rakefile CHANGED
@@ -1,3 +1,2 @@
1
- require "bundler/setup"
2
-
3
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
@@ -0,0 +1,16 @@
1
+ module ActiveAgent
2
+ module ActionPrompt
3
+ class Action
4
+ attr_accessor :agent_name, :name, :params
5
+
6
+ def initialize(attributes = {})
7
+ @name = attributes.fetch(:name, "")
8
+ @params = attributes.fetch(:params, {})
9
+ end
10
+
11
+ def perform_action
12
+ agent_name.constantize
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,127 @@
1
+ # require "abstract_controller"
2
+ # require "active_support/core_ext/string/inflections"
3
+
4
+ # module ActiveAgent
5
+ # module ActionPrompt
6
+ # extend ::ActiveSupport::Autoload
7
+
8
+ # eager_autoload do
9
+ # autoload :Collector
10
+ # autoload :Message
11
+ # autoload :Prompt
12
+ # autoload :PromptHelper
13
+ # end
14
+
15
+ # autoload :Base
16
+
17
+ # extend ActiveSupport::Concern
18
+
19
+ # included do
20
+ # include AbstractController::Rendering
21
+ # include AbstractController::Layouts
22
+ # include AbstractController::Helpers
23
+ # include AbstractController::Translation
24
+ # include AbstractController::AssetPaths
25
+ # include AbstractController::Callbacks
26
+ # include AbstractController::Caching
27
+
28
+ # include ActionView::Layouts
29
+
30
+ # helper ActiveAgent::PromptHelper
31
+ # # class_attribute :default_params, default: {
32
+ # # content_type: "text/plain",
33
+ # # parts_order: ["text/plain", "text/html", "application/json"]
34
+ # # }.freeze
35
+ # end
36
+
37
+ # # # def self.prompt(headers = {}, &)
38
+ # # # new.prompt(headers, &)
39
+ # # # end
40
+
41
+ # # def prompt(headers = {}, &block)
42
+ # # return @_message if @_prompt_was_called && headers.blank? && !block
43
+
44
+ # # headers = apply_defaults(headers)
45
+
46
+ # # @_message = ActiveAgent::ActionPrompt::Prompt.new
47
+
48
+ # # assign_headers_to_message(@_message, headers)
49
+
50
+ # # responses = collect_responses(headers, &block)
51
+
52
+ # # @_prompt_was_called = true
53
+
54
+ # # create_parts_from_responses(@_message, responses)
55
+
56
+ # # @_message
57
+ # # end
58
+
59
+ # private
60
+
61
+ # def apply_defaults(headers)
62
+ # headers.reverse_merge(self.class.default_params)
63
+ # end
64
+
65
+ # def assign_headers_to_message(message, headers)
66
+ # assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
67
+ # assignable.each { |k, v| message.send(:"#{k}=", v) }
68
+ # end
69
+
70
+ # def collect_responses(headers, &block)
71
+ # if block
72
+ # collect_responses_from_block(headers, &block)
73
+ # elsif headers[:body]
74
+ # collect_responses_from_text(headers)
75
+ # else
76
+ # collect_responses_from_templates(headers)
77
+ # end
78
+ # end
79
+
80
+ # def collect_responses_from_block(headers, &block)
81
+ # templates_name = headers[:template_name] || action_name
82
+ # collector = ActiveAgent::ActionPrompt::Collector.new(lookup_context) { render(templates_name) }
83
+ # yield(collector)
84
+ # collector.responses
85
+ # end
86
+
87
+ # def collect_responses_from_text(headers)
88
+ # [{
89
+ # body: headers.delete(:body),
90
+ # content_type: headers[:content_type] || "text/plain"
91
+ # }]
92
+ # end
93
+
94
+ # def collect_responses_from_templates(headers)
95
+ # templates_path = headers[:template_path] || self.class.name.sub(/Agent$/, "").underscore
96
+ # templates_name = headers[:template_name] || action_name
97
+ # each_template(Array(templates_path), templates_name).map do |template|
98
+ # format = template.format || formats.first
99
+ # {
100
+ # body: render(template: template, formats: [format]),
101
+ # content_type: Mime[format].to_s
102
+ # }
103
+ # end
104
+ # end
105
+
106
+ # def each_template(paths, name, &)
107
+ # templates = lookup_context.find_all(name, paths)
108
+ # if templates.empty?
109
+ # raise ActionView::MissingTemplate.new(paths, name, paths, false, "prompt")
110
+ # else
111
+ # templates.uniq(&:format).each(&)
112
+ # end
113
+ # end
114
+
115
+ # def create_parts_from_responses(message, responses)
116
+ # responses.each do |response|
117
+ # message.add_part(response[:body], content_type: response[:content_type])
118
+ # end
119
+ # end
120
+
121
+ # class TestAgent
122
+ # class << self
123
+ # attr_accessor :generations
124
+ # end
125
+ # end
126
+ # end
127
+ # end
@@ -7,31 +7,22 @@ module ActiveAgent
7
7
 
8
8
  def initialize(attributes = {})
9
9
  @content = attributes[:content] || ""
10
- @role = attributes[:role] || "user"
10
+ @role = attributes[:role] || :user
11
11
  @name = attributes[:name]
12
- @action_requested = attributes[:function_call]
13
- @requested_actions = attributes[:tool_calls] || []
12
+ @agent_class = attributes[:agent_class]
13
+ @requested_actions = attributes[:requested_actions] || []
14
+ @action_requested = @requested_actions.any?
14
15
  validate_role
15
16
  end
16
17
 
17
18
  def to_h
18
19
  hash = {role: role, content: content}
19
20
  hash[:name] = name if name
20
- hash[:action_requested] = action_requested if action_requested
21
+ hash[:action_requested] = requested_actions.any?
21
22
  hash[:requested_actions] = requested_actions if requested_actions.any?
22
23
  hash
23
24
  end
24
25
 
25
- def perform_actions
26
- requested_actions.each do |action|
27
- action.call(self) if action.respond_to?(:call)
28
- end
29
- end
30
-
31
- def action_requested?
32
- action_requested.present? || requested_actions.any?
33
- end
34
-
35
26
  private
36
27
 
37
28
  def validate_role
@@ -1,10 +1,9 @@
1
- # lib/active_agent/action_prompt/prompt.rb
2
1
  require_relative "message"
3
2
 
4
3
  module ActiveAgent
5
4
  module ActionPrompt
6
5
  class Prompt
7
- attr_accessor :actions, :body, :content_type, :instructions, :message, :messages, :options, :mime_version, :charset, :context
6
+ attr_accessor :actions, :body, :content_type, :instructions, :message, :messages, :options, :mime_version, :charset, :context, :parts
8
7
 
9
8
  def initialize(attributes = {})
10
9
  @options = attributes.fetch(:options, {})
@@ -31,11 +30,10 @@ module ActiveAgent
31
30
  @message.to_s
32
31
  end
33
32
 
34
- def add_part(part)
35
- message = Message.new(content: part[:body], role: :user)
36
- prompt_part = self.class.new(message: message, content: message.content, content_type: part[:content_type], chartset: part[:charset])
33
+ def add_part(prompt_part)
34
+ @message = prompt_part.message
37
35
 
38
- set_message if @content_type == part[:content_type] && @message.content
36
+ set_message if @content_type == prompt_part.content_type && @message.content.present?
39
37
 
40
38
  @parts << prompt_part
41
39
  end
@@ -67,12 +65,12 @@ module ActiveAgent
67
65
  end
68
66
 
69
67
  def set_message
70
- if @body.is_a?(String) && !@message.content
71
- @message = Message.new(content: @body, role: :user)
72
- elsif @message.is_a? String
68
+ if @message.is_a? String
73
69
  @message = Message.new(content: @message, role: :user)
70
+ elsif @body.is_a?(String) && @message.content.blank?
71
+ @message = Message.new(content: @body, role: :user)
74
72
  end
75
- @messages = [@message]
73
+ @messages << @message
76
74
  end
77
75
  end
78
76
  end
@@ -67,11 +67,7 @@ module ActiveAgent
67
67
  }.freeze
68
68
 
69
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.
70
+ # Register one or more Observers which will be notified when prompt is generated.
75
71
  def register_observers(*observers)
76
72
  observers.flatten.compact.each { |observer| register_observer(observer) }
77
73
  end
@@ -81,7 +77,7 @@ module ActiveAgent
81
77
  observers.flatten.compact.each { |observer| unregister_observer(observer) }
82
78
  end
83
79
 
84
- # Register one or more Interceptors which will be called before mail is sent.
80
+ # Register one or more Interceptors which will be called before prompt is sent.
85
81
  def register_interceptors(*interceptors)
86
82
  interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) }
87
83
  end
@@ -91,32 +87,32 @@ module ActiveAgent
91
87
  interceptors.flatten.compact.each { |interceptor| unregister_interceptor(interceptor) }
92
88
  end
93
89
 
94
- # Register an Observer which will be notified when mail is delivered.
90
+ # Register an Observer which will be notified when prompt is generated.
95
91
  # Either a class, string, or symbol can be passed in as the Observer.
96
92
  # If a string or symbol is passed in it will be camelized and constantized.
97
93
  def register_observer(observer)
98
- Mail.register_observer(observer_class_for(observer))
94
+ Prompt.register_observer(observer_class_for(observer))
99
95
  end
100
96
 
101
97
  # Unregister a previously registered Observer.
102
98
  # Either a class, string, or symbol can be passed in as the Observer.
103
99
  # If a string or symbol is passed in it will be camelized and constantized.
104
100
  def unregister_observer(observer)
105
- Mail.unregister_observer(observer_class_for(observer))
101
+ Prompt.unregister_observer(observer_class_for(observer))
106
102
  end
107
103
 
108
- # Register an Interceptor which will be called before mail is sent.
104
+ # Register an Interceptor which will be called before prompt is sent.
109
105
  # Either a class, string, or symbol can be passed in as the Interceptor.
110
106
  # If a string or symbol is passed in it will be camelized and constantized.
111
107
  def register_interceptor(interceptor)
112
- Mail.register_interceptor(observer_class_for(interceptor))
108
+ Prompt.register_interceptor(observer_class_for(interceptor))
113
109
  end
114
110
 
115
111
  # Unregister a previously registered Interceptor.
116
112
  # Either a class, string, or symbol can be passed in as the Interceptor.
117
113
  # If a string or symbol is passed in it will be camelized and constantized.
118
114
  def unregister_interceptor(interceptor)
119
- Mail.unregister_interceptor(observer_class_for(interceptor))
115
+ Prompt.unregister_interceptor(observer_class_for(interceptor))
120
116
  end
121
117
 
122
118
  def observer_class_for(value) # :nodoc:
@@ -133,6 +129,7 @@ module ActiveAgent
133
129
  def generate_with(provider, **options)
134
130
  self.generation_provider = provider
135
131
  self.options = (options || {}).merge(options)
132
+ generation_provider.config.merge!(options)
136
133
  end
137
134
 
138
135
  def stream_with(&stream)
@@ -208,6 +205,35 @@ module ActiveAgent
208
205
  def perform_generation
209
206
  context.options.merge(options)
210
207
  generation_provider.generate(context) if context && generation_provider
208
+ handle_response(generation_provider.response)
209
+ # perform_actions(requested_actions: context.message.requested_actions)
210
+ # update_context(context)
211
+ # if context.requested_actions.present?
212
+ # context.requested_actions.each do |action|
213
+ # perform_action(action)
214
+ # end
215
+ # end
216
+ generation_provider.response
217
+ end
218
+
219
+ def handle_response(response)
220
+ perform_actions(requested_actions: response.message.requested_actions) if response.message.requested_actions.present?
221
+ update_context(response)
222
+ end
223
+
224
+ def update_context(response)
225
+ context.message = response.message
226
+ context.messages << response.message
227
+ end
228
+
229
+ def perform_actions(requested_actions:)
230
+ requested_actions.each do |action|
231
+ perform_action(action)
232
+ end
233
+ end
234
+
235
+ def perform_action(action)
236
+ process(action.name, *action.params)
211
237
  end
212
238
 
213
239
  def initialize
@@ -286,6 +312,10 @@ module ActiveAgent
286
312
  end
287
313
  end
288
314
 
315
+ def prompt_with(*)
316
+ context.update_context(*)
317
+ end
318
+
289
319
  def prompt(headers = {}, &block)
290
320
  return context if @_prompt_was_called && headers.blank? && !block
291
321
 
@@ -296,6 +326,7 @@ module ActiveAgent
296
326
  context.charset = charset = headers[:charset]
297
327
 
298
328
  responses = collect_responses(headers, &block)
329
+
299
330
  @_prompt_was_called = true
300
331
 
301
332
  create_parts_from_responses(context, responses)
@@ -303,14 +334,15 @@ module ActiveAgent
303
334
  context.content_type = set_content_type(context, content_type, headers[:content_type])
304
335
  context.charset = charset
305
336
  context.actions = headers[:actions] || action_schemas
306
- binding.irb
307
337
  context
308
338
  end
309
-
339
+
310
340
  def action_schemas
311
341
  action_methods.map do |action|
312
- JSON.parse render_to_string(locals: {action_name: action}, action: action, formats: :json)
313
- end
342
+ if action != "prompt"
343
+ JSON.parse render_to_string(locals: {action_name: action}, action: action, formats: :json)
344
+ end
345
+ end.compact
314
346
  end
315
347
 
316
348
  private
@@ -385,12 +417,14 @@ module ActiveAgent
385
417
  templates_name = headers[:template_name] || action_name
386
418
 
387
419
  each_template(Array(templates_path), templates_name).map do |template|
420
+ next if template.format == :json
421
+
388
422
  format = template.format || formats.first
389
423
  {
390
424
  body: render(template: template, formats: [format]),
391
425
  content_type: Mime[format].to_s
392
426
  }
393
- end
427
+ end.compact
394
428
  end
395
429
 
396
430
  def each_template(paths, name, &)
@@ -403,7 +437,7 @@ module ActiveAgent
403
437
  end
404
438
 
405
439
  def create_parts_from_responses(context, responses)
406
- if responses.size > 1 && false
440
+ if responses.size > 1
407
441
  prompt_container = ActiveAgent::ActionPrompt::Prompt.new
408
442
  prompt_container.content_type = "multipart/alternative"
409
443
  responses.each { |r| insert_part(context, r, context.charset) }
@@ -415,7 +449,8 @@ module ActiveAgent
415
449
 
416
450
  def insert_part(container, response, charset)
417
451
  response[:charset] ||= charset
418
- container.add_part(response)
452
+ prompt = ActiveAgent::ActionPrompt::Prompt.new(response)
453
+ container.add_part(prompt)
419
454
  end
420
455
 
421
456
  # This and #instrument_name is for caching instrument
@@ -37,7 +37,7 @@ module ActiveAgent
37
37
  def generate_now!
38
38
  processed_agent.handle_exceptions do
39
39
  processed_agent.run_callbacks(:generate) do
40
- processed_agent..perform_generation
40
+ processed_agent.perform_generation!
41
41
  end
42
42
  end
43
43
  end
@@ -4,7 +4,7 @@ module ActiveAgent
4
4
  module GenerationProvider
5
5
  class Base
6
6
  class GenerationProviderError < StandardError; end
7
- attr_reader :client, :config, :prompt
7
+ attr_reader :client, :config, :prompt, :response
8
8
 
9
9
  def initialize(config)
10
10
  @config = config
@@ -19,10 +19,15 @@ module ActiveAgent
19
19
  private
20
20
 
21
21
  def handle_response(response)
22
- ActiveAgent::GenerationProvider::Response.new(message:, raw_response: response)
22
+ @response = ActiveAgent::GenerationProvider::Response.new(message:, raw_response: response)
23
23
  raise NotImplementedError, "Subclasses must implement the 'handle_response' method"
24
24
  end
25
25
 
26
+ def update_context(prompt:, message:, response:)
27
+ prompt.message = message
28
+ prompt.messages << message
29
+ end
30
+
26
31
  protected
27
32
 
28
33
  def prompt_parameters
@@ -1,8 +1,9 @@
1
1
  # lib/active_agent/generation_provider/open_ai_provider.rb
2
2
 
3
- require_relative "base"
4
3
  require "openai"
5
- require "active_agent/generation_provider/response"
4
+ require "active_agent/action_prompt/action"
5
+ require_relative "base"
6
+ require_relative "response"
6
7
 
7
8
  module ActiveAgent
8
9
  module GenerationProvider
@@ -16,24 +17,53 @@ module ActiveAgent
16
17
 
17
18
  def generate(prompt)
18
19
  @prompt = prompt
19
- parameters = prompt_parameters.merge(model: @model_name)
20
20
 
21
21
  # parameters[:instructions] = prompt.instructions.content if prompt.instructions.present?
22
22
 
23
+ chat_prompt(parameters: prompt_parameters)
24
+ rescue => e
25
+ raise GenerationProviderError, e.message
26
+ end
27
+
28
+ def chat_prompt(parameters: prompt_parameters)
23
29
  parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
30
+ chat_response(@client.chat(parameters: parameters))
31
+ end
24
32
 
25
- response = @client.chat(parameters: parameters)
26
- handle_response(response)
33
+ def embed(prompt)
34
+ @prompt = prompt
35
+
36
+ embeddings_prompt(parameters: embeddings_parameters)
27
37
  rescue => e
28
- raise GenerationProviderError, e.message
38
+ raise GenerationProviderError, e.message
39
+ end
40
+
41
+ def embeddings_parameters(input: prompt.message.content, model: "text-embedding-ada-002")
42
+ {
43
+ model: model,
44
+ input: input
45
+ }
46
+ end
47
+
48
+ def embeddings_response(response)
49
+ message = Message.new(content:response.dig("data", 0, "embedding"), role: "assistant")
50
+
51
+ @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
52
+ end
53
+ def embeddings_prompt(parameters: )
54
+ binding.irb
55
+
56
+
57
+
58
+ embeddings_response(@client.embeddings(parameters: embeddings_parameters))
29
59
  end
30
60
 
31
61
  private
32
62
 
33
63
  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"]
64
+ # prompt.options[:stream] will define a proc found in prompt at runtime
65
+ # config[:stream] will define a proc found in config. stream would come from an Agent class's generate_with or stream_with method calls
66
+ agent_stream = prompt.options[:stream] || config["stream"]
37
67
  proc do |chunk, bytesize|
38
68
  # Provider parsing logic here
39
69
  new_content = chunk.dig("choices", 0, "delta", "content")
@@ -45,23 +75,41 @@ module ActiveAgent
45
75
  end
46
76
  end
47
77
 
48
- def prompt_parameters
78
+ def prompt_parameters(model: @model_name, messages: @prompt.messages, temperature: @config["temperature"] || 0.7, tools: @prompt.actions)
49
79
  {
50
- messages: @prompt.messages,
51
- temperature: @config["temperature"] || 0.7,
52
- tools: @prompt.actions
80
+ model: model,
81
+ messages: messages,
82
+ temperature: temperature,
83
+ tools: tools
53
84
  }
54
85
  end
55
86
 
56
- def handle_response(response)
87
+ def chat_response(response)
57
88
  message_json = response.dig("choices", 0, "message")
58
89
  message = ActiveAgent::ActionPrompt::Message.new(
59
90
  content: message_json["content"],
60
91
  role: message_json["role"],
61
- action_reqested: message_json["function_call"],
62
- requested_actions: message_json["tool_calls"]
92
+ action_requested: message_json["finish_reason"] == "tool_calls",
93
+ requested_actions: handle_actions(message_json["tool_calls"])
63
94
  )
64
- ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
95
+
96
+ update_context(prompt: prompt, message: message, response: response)
97
+
98
+ @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
99
+ end
100
+
101
+ def handle_actions(tool_calls)
102
+ if tool_calls
103
+ tool_calls.map do |tool_call|
104
+ ActiveAgent::ActionPrompt::Action.new(
105
+ name: tool_call.dig("function", "name"),
106
+ params: JSON.parse(
107
+ tool_call.dig("function", "arguments"),
108
+ {symbolize_names: true}
109
+ )
110
+ )
111
+ end
112
+ end
65
113
  end
66
114
  end
67
115
  end
@@ -16,7 +16,6 @@ module ActiveAgent
16
16
  config = ActiveAgent.config[provider_name.to_s] || ActiveAgent.config[ENV["RAILS_ENV"]][provider_name.to_s]
17
17
 
18
18
  raise "Configuration not found for provider: #{provider_name}" unless config
19
-
20
19
  config.merge!(options)
21
20
  configure_provider(config)
22
21
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0-rc1"
3
3
  end
data/lib/active_agent.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "yaml"
2
2
  require "abstract_controller"
3
+ require "active_agent/action_prompt"
4
+ require "active_agent/generation_provider"
3
5
  require "active_agent/version"
4
6
  require "active_agent/deprecator"
5
7
  require "active_agent/railtie" if defined?(Rails)
@@ -7,10 +7,10 @@ module ActiveAgent
7
7
 
8
8
  argument :actions, type: :array, default: [], banner: "method method"
9
9
 
10
- check_class_collision suffix: "Agent"
10
+ check_class_collision
11
11
 
12
12
  def create_agent_file
13
- template "agent.rb", File.join("app/agents", class_path, "#{file_name}_agent.rb")
13
+ template "agent.rb", File.join("app/agents", class_path, "#{file_name}.rb")
14
14
 
15
15
  in_root do
16
16
  if behavior == :invoke && !File.exist?(application_agent_file_name)
@@ -1,5 +1,5 @@
1
1
  <% module_namespacing do -%>
2
- class <%= class_name %>Agent < ApplicationAgent
2
+ class <%= class_name %> < ApplicationAgent
3
3
  <% actions.each_with_index do |action, index| -%>
4
4
  <% if index != 0 -%>
5
5
 
@@ -1,4 +1,5 @@
1
1
  <% module_namespacing do -%>
2
2
  class ApplicationAgent < ActiveAgent::Base
3
+ layout 'agent'
3
4
  end
4
5
  <% end %>
metadata CHANGED
@@ -1,99 +1,135 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0.pre.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-14 00:00:00.000000000 Z
11
+ date: 2025-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '7.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '7.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9.0'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: actionview
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - "~>"
37
+ - - ">="
32
38
  - !ruby/object:Gem::Version
33
39
  version: '7.2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '9.0'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - "~>"
47
+ - - ">="
39
48
  - !ruby/object:Gem::Version
40
49
  version: '7.2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '9.0'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: activesupport
43
55
  requirement: !ruby/object:Gem::Requirement
44
56
  requirements:
45
- - - "~>"
57
+ - - ">="
46
58
  - !ruby/object:Gem::Version
47
59
  version: '7.2'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '9.0'
48
63
  type: :runtime
49
64
  prerelease: false
50
65
  version_requirements: !ruby/object:Gem::Requirement
51
66
  requirements:
52
- - - "~>"
67
+ - - ">="
53
68
  - !ruby/object:Gem::Version
54
69
  version: '7.2'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '9.0'
55
73
  - !ruby/object:Gem::Dependency
56
74
  name: activemodel
57
75
  requirement: !ruby/object:Gem::Requirement
58
76
  requirements:
59
- - - "~>"
77
+ - - ">="
60
78
  - !ruby/object:Gem::Version
61
79
  version: '7.2'
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '9.0'
62
83
  type: :runtime
63
84
  prerelease: false
64
85
  version_requirements: !ruby/object:Gem::Requirement
65
86
  requirements:
66
- - - "~>"
87
+ - - ">="
67
88
  - !ruby/object:Gem::Version
68
89
  version: '7.2'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '9.0'
69
93
  - !ruby/object:Gem::Dependency
70
94
  name: activejob
71
95
  requirement: !ruby/object:Gem::Requirement
72
96
  requirements:
73
- - - "~>"
97
+ - - ">="
74
98
  - !ruby/object:Gem::Version
75
99
  version: '7.2'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '9.0'
76
103
  type: :runtime
77
104
  prerelease: false
78
105
  version_requirements: !ruby/object:Gem::Requirement
79
106
  requirements:
80
- - - "~>"
107
+ - - ">="
81
108
  - !ruby/object:Gem::Version
82
109
  version: '7.2'
110
+ - - "<"
111
+ - !ruby/object:Gem::Version
112
+ version: '9.0'
83
113
  - !ruby/object:Gem::Dependency
84
114
  name: rails
85
115
  requirement: !ruby/object:Gem::Requirement
86
116
  requirements:
87
- - - "~>"
117
+ - - ">="
88
118
  - !ruby/object:Gem::Version
89
119
  version: '7.2'
120
+ - - "<"
121
+ - !ruby/object:Gem::Version
122
+ version: '9.0'
90
123
  type: :runtime
91
124
  prerelease: false
92
125
  version_requirements: !ruby/object:Gem::Requirement
93
126
  requirements:
94
- - - "~>"
127
+ - - ">="
95
128
  - !ruby/object:Gem::Version
96
129
  version: '7.2'
130
+ - - "<"
131
+ - !ruby/object:Gem::Version
132
+ version: '9.0'
97
133
  description: A simple way to perform long running LLM background jobs and streaming
98
134
  responses
99
135
  email: jusbowen@gmail.com
@@ -107,6 +143,7 @@ files:
107
143
  - lib/active_agent/README.md
108
144
  - lib/active_agent/action_prompt.rb
109
145
  - lib/active_agent/action_prompt/README.md
146
+ - lib/active_agent/action_prompt/action.rb
110
147
  - lib/active_agent/action_prompt/base.rb
111
148
  - lib/active_agent/action_prompt/collector.rb
112
149
  - lib/active_agent/action_prompt/message.rb
@@ -143,7 +180,7 @@ files:
143
180
  - lib/generators/active_agent/templates/agent_spec.rb.tt
144
181
  - lib/generators/active_agent/templates/agent_test.rb.tt
145
182
  - lib/generators/active_agent/templates/application_agent.rb.tt
146
- homepage: https://rubygems.org/gems/activeagent
183
+ homepage: https://activeagents.ai
147
184
  licenses:
148
185
  - MIT
149
186
  metadata: {}