activeagent 0.1.1 → 0.2.1.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +54 -118
- data/Rakefile +1 -2
- data/lib/active_agent/action_prompt/action.rb +16 -0
- data/lib/active_agent/action_prompt/base.rb +127 -0
- data/lib/active_agent/action_prompt/message.rb +5 -14
- data/lib/active_agent/action_prompt/prompt.rb +8 -10
- data/lib/active_agent/base.rb +54 -19
- data/lib/active_agent/generation.rb +1 -1
- data/lib/active_agent/generation_provider/base.rb +7 -2
- data/lib/active_agent/generation_provider/open_ai_provider.rb +65 -17
- data/lib/active_agent/generation_provider.rb +0 -1
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/active_agent/agent_generator.rb +2 -2
- data/lib/generators/active_agent/templates/agent.rb.tt +1 -1
- data/lib/generators/active_agent/templates/application_agent.rb.tt +1 -0
- metadata +55 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b07eb2c788cd8528cbb804f01ecddb3d0c1cb543bc8151df1db7351e5d7e21a3
|
4
|
+
data.tar.gz: 7cd9fe9ea001ea362af387f86b8270d53dfd5446a791e9c444739f87abcc31c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ad62fd71161308a780d7558d0b283a99ec3e07310955906da6b7add4c347bea20d9896b5a92ceb6f87eb010b0b86e4e8166c70459ea27a84de4f847c892e655
|
7
|
+
data.tar.gz: 8754c59f6606cceefa745c038a32af7fc037b359223baec64838a03a07dbd34b74b6780b86166697947e3b955306ff1d653afa2f67cc08d0e36ebbb106ab5f4e
|
data/README.md
CHANGED
@@ -1,153 +1,89 @@
|
|
1
|
-
#
|
1
|
+
# Active Agent: README.md
|
2
2
|
|
3
|
-
|
3
|
+
# Active Agent
|
4
4
|
|
5
|
-
##
|
5
|
+
## Agent
|
6
6
|
|
7
|
-
|
7
|
+
Create agents that take instructions, prompts, and perform actions
|
8
8
|
|
9
|
-
|
10
|
-
gem 'active_agent'
|
11
|
-
```
|
12
|
-
|
13
|
-
And then execute:
|
9
|
+
### Generation Provider
|
14
10
|
|
15
|
-
```
|
16
|
-
|
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
|
-
|
17
|
+
`generate_with` sets the generation provider’s completion generation model and parameters.
|
21
18
|
|
22
|
-
|
23
|
-
```
|
24
|
-
rails generate agent inventory search
|
25
|
-
```
|
19
|
+
`completion_response = SupportAgent.prompt(‘Help me!’).generate_now`
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
28
|
+
`embed_with` sets the generation provider’s embedding generation model and parameters.
|
36
29
|
|
37
|
-
|
30
|
+
`embedding_response = SupportAgent.prompt(‘Help me!’).embed_now`
|
38
31
|
|
39
|
-
|
40
|
-
#
|
32
|
+
### Instructions
|
41
33
|
|
42
|
-
|
34
|
+
Instructions are system prompts that predefine the agent’s intention.
|
43
35
|
|
36
|
+
### Prompt
|
44
37
|
|
45
|
-
|
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
|
-
|
49
|
-
@items = Item.search(params[:query])
|
50
|
-
end
|
40
|
+
`SupportAgent.prompt(“What does CRUD and REST mean?”)`
|
51
41
|
|
52
|
-
|
53
|
-
@organization = Organization.find(params[:account_id])
|
54
|
-
prompt
|
55
|
-
end
|
56
|
-
end
|
57
|
-
```
|
42
|
+
### Queue Generation
|
58
43
|
|
59
|
-
|
44
|
+
Active Agent also supports queued generation with Active Job using a common Generation Job interface.
|
60
45
|
|
61
|
-
|
46
|
+
### Perform actions
|
62
47
|
|
63
|
-
|
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
|
-
|
68
|
-
@content = content
|
69
|
-
@context = context
|
70
|
-
end
|
50
|
+
## Actions
|
71
51
|
|
72
|
-
|
73
|
-
|
74
|
-
|
52
|
+
```
|
53
|
+
def get_cat_image_base64
|
54
|
+
uri = URI("https://cataas.com/cat")
|
55
|
+
response = Net::HTTP.get_response(uri)
|
75
56
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
79
|
+
### Basic
|
131
80
|
|
132
|
-
|
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
|
-
|
83
|
+
### HTML
|
147
84
|
|
148
|
-
|
85
|
+
### Action Schema JSON
|
149
86
|
|
150
|
-
|
87
|
+
response = SupportAgent.prompt(‘show me a picture of a cat’).generate_now
|
151
88
|
|
152
|
-
|
153
|
-
```
|
89
|
+
response.message
|
data/Rakefile
CHANGED
@@ -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] ||
|
10
|
+
@role = attributes[:role] || :user
|
11
11
|
@name = attributes[:name]
|
12
|
-
@
|
13
|
-
@requested_actions = attributes[:
|
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] =
|
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(
|
35
|
-
message =
|
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 ==
|
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 @
|
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
|
73
|
+
@messages << @message
|
76
74
|
end
|
77
75
|
end
|
78
76
|
end
|
data/lib/active_agent/base.rb
CHANGED
@@ -67,11 +67,7 @@ module ActiveAgent
|
|
67
67
|
}.freeze
|
68
68
|
|
69
69
|
class << self
|
70
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
101
|
+
Prompt.unregister_observer(observer_class_for(observer))
|
106
102
|
end
|
107
103
|
|
108
|
-
# Register an Interceptor which will be called before
|
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
|
-
|
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
|
-
|
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
|
-
|
313
|
-
|
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
|
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
|
-
|
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
|
@@ -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/
|
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
|
-
|
26
|
-
|
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.
|
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.
|
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
|
-
|
51
|
-
|
52
|
-
|
80
|
+
model: model,
|
81
|
+
messages: messages,
|
82
|
+
temperature: temperature,
|
83
|
+
tools: tools
|
53
84
|
}
|
54
85
|
end
|
55
86
|
|
56
|
-
def
|
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
|
-
|
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
|
-
|
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
|
data/lib/active_agent/version.rb
CHANGED
data/lib/active_agent.rb
CHANGED
@@ -7,10 +7,10 @@ module ActiveAgent
|
|
7
7
|
|
8
8
|
argument :actions, type: :array, default: [], banner: "method method"
|
9
9
|
|
10
|
-
check_class_collision
|
10
|
+
check_class_collision
|
11
11
|
|
12
12
|
def create_agent_file
|
13
|
-
template "agent.rb", File.join("app/agents", class_path, "#{file_name}
|
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)
|
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.
|
4
|
+
version: 0.2.1.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:
|
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,10 +180,12 @@ 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://
|
183
|
+
homepage: https://activeagents.ai
|
147
184
|
licenses:
|
148
185
|
- MIT
|
149
|
-
metadata:
|
186
|
+
metadata:
|
187
|
+
source_code_uri: https://github.com/activeagents/activeagent
|
188
|
+
bug_tracker_uri: https://github.com/activeagents/activeagent/issues
|
150
189
|
post_install_message:
|
151
190
|
rdoc_options: []
|
152
191
|
require_paths:
|