activeagent 0.5.0rc1 → 0.5.0rc3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +179 -3
- data/lib/active_agent/action_prompt/base.rb +21 -17
- data/lib/active_agent/action_prompt/prompt.rb +4 -5
- data/lib/active_agent/callbacks.rb +12 -17
- data/lib/active_agent/generation_provider/anthropic_provider.rb +10 -5
- data/lib/active_agent/generation_provider/base.rb +1 -1
- data/lib/active_agent/generation_provider/ollama_provider.rb +2 -2
- data/lib/active_agent/generation_provider/open_ai_provider.rb +8 -9
- data/lib/active_agent/generation_provider/open_router_provider.rb +2 -2
- data/lib/active_agent/generation_provider/response.rb +28 -0
- data/lib/active_agent/generation_provider.rb +12 -5
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/USAGE +28 -6
- data/lib/generators/active_agent/USAGE +36 -10
- data/lib/generators/active_agent/agent_generator.rb +5 -1
- data/lib/generators/active_agent/install_generator.rb +9 -3
- data/lib/generators/active_agent/templates/application_agent.rb.tt +3 -3
- data/lib/generators/erb/agent_generator.rb +2 -2
- data/lib/generators/erb/install_generator.rb +25 -4
- data/lib/generators/test_unit/install_generator.rb +1 -1
- metadata +1 -2
- data/lib/generators/erb/templates/application_agent.rb.tt +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f3087fa32e11e33dd1f48e093a8d92b38e11264bf403faa65f4a57c8e3b2a4b
|
4
|
+
data.tar.gz: 57ce989b2279b23d56ab3b40eba65877ec336bc056cc375f869b1caf301aad2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25c0437d878f5c2452276614c23c868dfdcec6667906e546b13176916fd12edd30ae4f6c21296ad3a724f5382e82a71a84594b4563165076aa5b50c52b98f7a2
|
7
|
+
data.tar.gz: 0cef9d5c876bf9d235c0090dfd4d873da3d0508c8c77820c70d408df2595eae7d3e167ffe65644ebcc7faa5b5e361be81c56446ad5c106877a2d651407a6d8a9
|
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
<picture>
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/2bad263a-c09f-40b6-94ba-fff8e346d65d">
|
3
|
+
<img alt="activeagents_banner" src="https://github.com/user-attachments/assets/0ebbaa2f-c6bf-4d40-bb77-931015a14be3">
|
4
|
+
</picture>
|
5
|
+
*Build AI in Rails*
|
6
|
+
|
7
|
+
|
3
8
|
>
|
4
9
|
> *Now Agents are Controllers*
|
5
10
|
>
|
@@ -9,4 +14,175 @@
|
|
9
14
|
Active Agent provides that missing AI layer in the Rails framework, offering a structured approach to building AI-powered applications through Agent Oriented Programming. **Now Agents are Controllers!** Designing applications using agents allows developers to create modular, reusable components that can be easily integrated into existing systems. This approach promotes code reusability, maintainability, and scalability, making it easier to build complex AI-driven applications with the Object Oriented Ruby code you already use today.
|
10
15
|
|
11
16
|
## Documentation
|
12
|
-
[docs.activeagents.ai](https://docs.activeagents.ai) - The official documentation site for Active Agent.
|
17
|
+
[docs.activeagents.ai](https://docs.activeagents.ai) - The official documentation site for Active Agent.
|
18
|
+
|
19
|
+
## Getting Started
|
20
|
+
|
21
|
+
### Installation
|
22
|
+
|
23
|
+
Use bundler to add activeagent to your Gemfile and install:
|
24
|
+
```bash
|
25
|
+
bundle add activeagent
|
26
|
+
```
|
27
|
+
|
28
|
+
Add the generation provider gem you want to use:
|
29
|
+
|
30
|
+
```bash
|
31
|
+
# OpenAI
|
32
|
+
bundle add ruby-openai
|
33
|
+
|
34
|
+
# Anthropic
|
35
|
+
bundle add ruby-anthropic
|
36
|
+
|
37
|
+
# Ollama (uses OpenAI-compatible API)
|
38
|
+
bundle add ruby-openai
|
39
|
+
|
40
|
+
# OpenRouter (uses OpenAI-compatible API)
|
41
|
+
bundle add ruby-openai
|
42
|
+
```
|
43
|
+
|
44
|
+
### Setup
|
45
|
+
|
46
|
+
Run the install generator to create the necessary configuration files:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
rails generate active_agent:install
|
50
|
+
```
|
51
|
+
|
52
|
+
This creates:
|
53
|
+
- `config/active_agent.yml`: Configuration file for generation providers
|
54
|
+
- `app/agents`: Directory for your agent classes
|
55
|
+
- `app/views/agent_*`: Directory for agent prompt/view templates
|
56
|
+
|
57
|
+
### Quick Example
|
58
|
+
|
59
|
+
Define an application agent:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class ApplicationAgent < ActiveAgent::Base
|
63
|
+
generate_with :openai,
|
64
|
+
instructions: "You are a helpful assistant.",
|
65
|
+
model: "gpt-4o-mini",
|
66
|
+
temperature: 0.7
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
Use your agent:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
message = "Test Application Agent"
|
74
|
+
prompt = ApplicationAgent.with(message: message).prompt_context
|
75
|
+
response = prompt.generate_now
|
76
|
+
```
|
77
|
+
|
78
|
+
### Your First Agent
|
79
|
+
|
80
|
+
Generate a new agent:
|
81
|
+
|
82
|
+
```bash
|
83
|
+
rails generate active_agent:agent TravelAgent search book confirm
|
84
|
+
```
|
85
|
+
|
86
|
+
This creates an agent with actions that can be called:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
class TravelAgent < ApplicationAgent
|
90
|
+
def search
|
91
|
+
# Your search logic here
|
92
|
+
prompt
|
93
|
+
end
|
94
|
+
|
95
|
+
def book
|
96
|
+
# Your booking logic here
|
97
|
+
prompt
|
98
|
+
end
|
99
|
+
|
100
|
+
def confirm
|
101
|
+
# Your confirmation logic here
|
102
|
+
prompt
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Configuration
|
108
|
+
|
109
|
+
Configure generation providers in `config/active_agent.yml`:
|
110
|
+
|
111
|
+
```yaml
|
112
|
+
development:
|
113
|
+
openai:
|
114
|
+
service: "OpenAI"
|
115
|
+
api_key: <%= Rails.application.credentials.dig(:openai, :api_key) %>
|
116
|
+
model: "gpt-4o-mini"
|
117
|
+
embeddings_model: "text-embedding-3-small"
|
118
|
+
|
119
|
+
anthropic:
|
120
|
+
service: "Anthropic"
|
121
|
+
api_key: <%= Rails.application.credentials.dig(:anthropic, :api_key) %>
|
122
|
+
model: "claude-3-5-sonnet"
|
123
|
+
|
124
|
+
ollama:
|
125
|
+
service: "Ollama"
|
126
|
+
model: "llama3.2"
|
127
|
+
embeddings_model: "nomic-embed-text"
|
128
|
+
host: "http://localhost:11434"
|
129
|
+
```
|
130
|
+
|
131
|
+
## Features
|
132
|
+
|
133
|
+
- **Agent-Oriented Programming**: Build AI applications using familiar Rails patterns
|
134
|
+
- **Multiple Provider Support**: Works with OpenAI, Anthropic, Ollama, and more
|
135
|
+
- **Action-Based Design**: Define agent capabilities through actions
|
136
|
+
- **View Templates**: Use ERB templates for prompts (text, JSON, HTML)
|
137
|
+
- **Streaming Support**: Real-time response streaming with ActionCable
|
138
|
+
- **Tool/Function Calling**: Agents can use tools to interact with external services
|
139
|
+
- **Context Management**: Maintain conversation history across interactions
|
140
|
+
- **Structured Output**: Define JSON schemas for predictable responses
|
141
|
+
|
142
|
+
## Examples
|
143
|
+
|
144
|
+
### Data Extraction
|
145
|
+
Extract structured data from images, PDFs, and text:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
prompt = DataExtractionAgent.with(
|
149
|
+
output_schema: :chart_schema,
|
150
|
+
image_path: Rails.root.join("sales_chart.png")
|
151
|
+
).parse_content
|
152
|
+
```
|
153
|
+
|
154
|
+
### Translation
|
155
|
+
Translate text between languages:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
response = TranslationAgent.with(
|
159
|
+
message: "Hi, I'm Justin",
|
160
|
+
locale: "japanese"
|
161
|
+
).translate.generate_now
|
162
|
+
```
|
163
|
+
|
164
|
+
### Tool Usage
|
165
|
+
Agents can use tools to perform actions:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# Agent with tool support
|
169
|
+
message = "Show me a cat"
|
170
|
+
prompt = SupportAgent.with(message: message).prompt_context
|
171
|
+
response = prompt.generate_now
|
172
|
+
# Response includes tool call results
|
173
|
+
```
|
174
|
+
|
175
|
+
## Learn More
|
176
|
+
|
177
|
+
- [Documentation](https://docs.activeagents.ai)
|
178
|
+
- [Getting Started Guide](https://docs.activeagents.ai/docs/getting-started)
|
179
|
+
- [API Reference](https://docs.activeagents.ai/docs/framework)
|
180
|
+
- [Examples](https://docs.activeagents.ai/docs/agents)
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
185
|
+
|
186
|
+
## License
|
187
|
+
|
188
|
+
Active Agent is released under the [MIT License](LICENSE).
|
@@ -113,7 +113,6 @@ module ActiveAgent
|
|
113
113
|
inherited_options = (self.options || {}).except(:instructions)
|
114
114
|
self.options = inherited_options.merge(options)
|
115
115
|
end
|
116
|
-
self.options[:stream] = new.agent_stream if self.options[:stream]
|
117
116
|
end
|
118
117
|
|
119
118
|
def stream_with(&stream)
|
@@ -182,7 +181,9 @@ module ActiveAgent
|
|
182
181
|
attr_internal :context
|
183
182
|
|
184
183
|
def agent_stream
|
185
|
-
proc do |message, delta, stop|
|
184
|
+
proc do |message, delta, stop, action_name|
|
185
|
+
@_action_name = action_name
|
186
|
+
|
186
187
|
run_stream_callbacks(message, delta, stop) do |message, delta, stop|
|
187
188
|
yield message, delta, stop if block_given?
|
188
189
|
end
|
@@ -221,7 +222,6 @@ module ActiveAgent
|
|
221
222
|
end
|
222
223
|
|
223
224
|
def update_context(response)
|
224
|
-
context.message = context.messages.last
|
225
225
|
ActiveAgent::GenerationProvider::Response.new(prompt: context)
|
226
226
|
end
|
227
227
|
|
@@ -233,16 +233,21 @@ module ActiveAgent
|
|
233
233
|
|
234
234
|
def perform_action(action)
|
235
235
|
current_context = context.clone
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
236
|
+
# Set params from the action for controller access
|
237
|
+
if action.params.is_a?(Hash)
|
238
|
+
self.params = action.params
|
239
|
+
end
|
240
|
+
process(action.name)
|
241
|
+
context.message.role = :tool
|
242
|
+
context.message.action_id = action.id
|
243
|
+
context.message.action_name = action.name
|
244
|
+
context.message.generation_id = action.id
|
245
|
+
current_context.message = context.message
|
246
|
+
current_context.messages << context.message
|
242
247
|
self.context = current_context
|
243
248
|
end
|
244
249
|
|
245
|
-
def initialize
|
250
|
+
def initialize # :nodoc:
|
246
251
|
super
|
247
252
|
@_prompt_was_called = false
|
248
253
|
@_context = ActiveAgent::ActionPrompt::Prompt.new(options: self.class.options || {})
|
@@ -300,20 +305,21 @@ module ActiveAgent
|
|
300
305
|
def prompt(headers = {}, &block)
|
301
306
|
return context if @_prompt_was_called && headers.blank? && !block
|
302
307
|
|
303
|
-
# Apply option hierarchy: prompt options > agent
|
308
|
+
# Apply option hierarchy: prompt options > agent > config options
|
304
309
|
merged_options = merge_options(headers)
|
305
310
|
raw_instructions = headers.has_key?(:instructions) ? headers[:instructions] : context.options[:instructions]
|
306
311
|
|
307
312
|
context.instructions = prepare_instructions(raw_instructions)
|
308
313
|
|
309
314
|
context.options.merge!(merged_options)
|
310
|
-
|
315
|
+
context.options[:stream] = agent_stream if context.options[:stream]
|
311
316
|
content_type = headers[:content_type]
|
312
317
|
|
313
318
|
headers = apply_defaults(headers)
|
314
319
|
context.messages = headers[:messages] || []
|
315
320
|
context.context_id = headers[:context_id]
|
316
321
|
context.params = params
|
322
|
+
context.action_name = action_name
|
317
323
|
|
318
324
|
context.output_schema = load_schema(headers[:output_schema], set_prefixes(headers[:output_schema], lookup_context.prefixes))
|
319
325
|
|
@@ -414,10 +420,7 @@ module ActiveAgent
|
|
414
420
|
# Only merge runtime options that are actually present (not nil)
|
415
421
|
runtime_options.each do |key, value|
|
416
422
|
next if value.nil?
|
417
|
-
|
418
|
-
if key == :stream && agent_options[:stream].is_a?(Proc) && !value.is_a?(Proc)
|
419
|
-
next
|
420
|
-
end
|
423
|
+
|
421
424
|
merged[key] = value
|
422
425
|
end
|
423
426
|
|
@@ -495,8 +498,8 @@ module ActiveAgent
|
|
495
498
|
def collect_responses_from_templates(headers)
|
496
499
|
templates_path = headers[:template_path] || self.class.agent_name
|
497
500
|
templates_name = headers[:template_name] || action_name
|
498
|
-
|
499
501
|
each_template(Array(templates_path), templates_name).map do |template|
|
502
|
+
next if template.format == :json && headers[:format] != :json
|
500
503
|
format = template.format || formats.first
|
501
504
|
{
|
502
505
|
body: render(template: template, formats: [ format ]),
|
@@ -532,6 +535,7 @@ module ActiveAgent
|
|
532
535
|
end
|
533
536
|
|
534
537
|
def prepare_instructions(instructions)
|
538
|
+
binding.irb
|
535
539
|
case instructions
|
536
540
|
when Hash
|
537
541
|
raise ArgumentError, "Expected `:template` key in instructions hash" unless instructions[:template]
|
@@ -4,7 +4,7 @@ module ActiveAgent
|
|
4
4
|
module ActionPrompt
|
5
5
|
class Prompt
|
6
6
|
attr_reader :messages, :instructions
|
7
|
-
attr_accessor :actions, :body, :content_type, :context_id, :message, :options, :mime_version, :charset, :context, :parts, :params, :action_choice, :agent_class, :output_schema
|
7
|
+
attr_accessor :actions, :body, :content_type, :context_id, :message, :options, :mime_version, :charset, :context, :parts, :params, :action_choice, :agent_class, :output_schema, :action_name
|
8
8
|
|
9
9
|
def initialize(attributes = {})
|
10
10
|
@options = attributes.fetch(:options, {})
|
@@ -26,6 +26,7 @@ module ActiveAgent
|
|
26
26
|
@parts = attributes.fetch(:parts, [])
|
27
27
|
@output_schema = attributes.fetch(:output_schema, nil)
|
28
28
|
@messages = Message.from_messages(@messages)
|
29
|
+
@action_name = attributes.fetch(:action_name, nil)
|
29
30
|
set_message if attributes[:message].is_a?(String) || @body.is_a?(String) && @message&.content
|
30
31
|
set_messages if @instructions.present?
|
31
32
|
end
|
@@ -56,10 +57,8 @@ module ActiveAgent
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def add_part(message)
|
59
|
-
|
60
|
-
|
61
|
-
set_message
|
62
|
-
end
|
60
|
+
@message = message
|
61
|
+
set_message
|
63
62
|
|
64
63
|
@parts << message
|
65
64
|
end
|
@@ -11,26 +11,21 @@ module ActiveAgent
|
|
11
11
|
end
|
12
12
|
|
13
13
|
module ClassMethods
|
14
|
-
# Defines a callback that will get called right before the
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def after_generation(*filters, &blk)
|
23
|
-
set_callback(:generation, :after, *filters, &blk)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Defines a callback that will get called around the prompt's generation method.
|
27
|
-
def around_generation(*filters, &blk)
|
28
|
-
set_callback(:generation, :around, *filters, &blk)
|
14
|
+
# # Defines a callback that will get called right before/after/around the
|
15
|
+
# # generation provider method.
|
16
|
+
[ :before, :after, :around ].each do |callback|
|
17
|
+
define_method "#{callback}_generation" do |*names, &blk|
|
18
|
+
_insert_callbacks(names, blk) do |name, options|
|
19
|
+
set_callback(:generation, callback, name, options)
|
20
|
+
end
|
21
|
+
end
|
29
22
|
end
|
30
23
|
|
31
24
|
# Defines a callback for handling streaming responses during generation
|
32
|
-
def on_stream(*
|
33
|
-
|
25
|
+
def on_stream(*names, &blk)
|
26
|
+
_insert_callbacks(names, blk) do |name, options|
|
27
|
+
set_callback(:stream, :before, name, options)
|
28
|
+
end
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
@@ -1,6 +1,12 @@
|
|
1
1
|
# lib/active_agent/generation_provider/anthropic_provider.rb
|
2
2
|
|
3
|
-
|
3
|
+
begin
|
4
|
+
gem "ruby-anthropic", "~> 0.4.2"
|
5
|
+
require "anthropic"
|
6
|
+
rescue LoadError
|
7
|
+
raise LoadError, "The 'ruby-anthropic' gem is required for AnthropicProvider. Please add it to your Gemfile and run `bundle install`."
|
8
|
+
end
|
9
|
+
|
4
10
|
require "active_agent/action_prompt/action"
|
5
11
|
require_relative "base"
|
6
12
|
require_relative "response"
|
@@ -10,9 +16,8 @@ module ActiveAgent
|
|
10
16
|
class AnthropicProvider < Base
|
11
17
|
def initialize(config)
|
12
18
|
super
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@client = Anthropic::Client.new(access_token: @api_key)
|
19
|
+
@access_token ||= config["api_key"] || config["access_token"] || Anthropic.configuration.access_token || ENV["ANTHROPIC_ACCESS_TOKEN"]
|
20
|
+
@client = Anthropic::Client.new(access_token: @access_token)
|
16
21
|
end
|
17
22
|
|
18
23
|
def generate(prompt)
|
@@ -40,7 +45,7 @@ module ActiveAgent
|
|
40
45
|
proc do |chunk|
|
41
46
|
if new_content = chunk.dig(:delta, :text)
|
42
47
|
message.content += new_content
|
43
|
-
agent_stream.call(message) if agent_stream.respond_to?(:call)
|
48
|
+
agent_stream.call(message, nil, false, prompt.action_name) if agent_stream.respond_to?(:call)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
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, :response
|
7
|
+
attr_reader :client, :config, :prompt, :response, :access_token, :model_name
|
8
8
|
|
9
9
|
def initialize(config)
|
10
10
|
@config = config
|
@@ -6,10 +6,10 @@ module ActiveAgent
|
|
6
6
|
class OllamaProvider < OpenAIProvider
|
7
7
|
def initialize(config)
|
8
8
|
@config = config
|
9
|
-
@
|
9
|
+
@access_token ||= config["api_key"] || config["access_token"] || ENV["OLLAMA_API_KEY"] || ENV["OLLAMA_ACCESS_TOKEN"]
|
10
10
|
@model_name = config["model"]
|
11
11
|
@host = config["host"] || "http://localhost:11434"
|
12
|
-
@client = OpenAI::Client.new(uri_base: @host, access_token: @
|
12
|
+
@client = OpenAI::Client.new(uri_base: @host, access_token: @access_token, log_errors: true)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -15,14 +15,13 @@ module ActiveAgent
|
|
15
15
|
class OpenAIProvider < Base
|
16
16
|
def initialize(config)
|
17
17
|
super
|
18
|
-
@
|
19
|
-
@
|
18
|
+
@host = config["host"] || nil
|
19
|
+
@access_token ||= config["api_key"] || config["access_token"] || OpenAI.configuration.access_token || ENV["OPENAI_ACCESS_TOKEN"]
|
20
|
+
@organization_id = config["organization_id"] || OpenAI.configuration.organization_id || ENV["OPENAI_ORGANIZATION_ID"]
|
21
|
+
@admin_token = config["admin_token"] || OpenAI.configuration.admin_token || ENV["OPENAI_ADMIN_TOKEN"]
|
22
|
+
@client = OpenAI::Client.new(access_token: @access_token, uri_base: @host, organization_id: @organization_id)
|
20
23
|
|
21
|
-
@
|
22
|
-
OpenAI::Client.new(uri_base: @host, access_token: @api_key)
|
23
|
-
else
|
24
|
-
OpenAI::Client.new(access_token: @api_key)
|
25
|
-
end
|
24
|
+
@model_name = config["model"] || "gpt-4o-mini"
|
26
25
|
end
|
27
26
|
|
28
27
|
def generate(prompt)
|
@@ -61,7 +60,7 @@ module ActiveAgent
|
|
61
60
|
message.generation_id = chunk.dig("id")
|
62
61
|
message.content += new_content
|
63
62
|
|
64
|
-
agent_stream.call(message, new_content, false) do |message, new_content|
|
63
|
+
agent_stream.call(message, new_content, false, prompt.action_name) do |message, new_content|
|
65
64
|
yield message, new_content if block_given?
|
66
65
|
end
|
67
66
|
elsif chunk.dig("choices", 0, "delta", "tool_calls") && chunk.dig("choices", 0, "delta", "role")
|
@@ -70,7 +69,7 @@ module ActiveAgent
|
|
70
69
|
@response = ActiveAgent::GenerationProvider::Response.new(prompt:, message:)
|
71
70
|
end
|
72
71
|
|
73
|
-
agent_stream.call(message, nil, true) do |message|
|
72
|
+
agent_stream.call(message, nil, true, prompt.action_name) do |message|
|
74
73
|
yield message, nil if block_given?
|
75
74
|
end
|
76
75
|
end
|
@@ -6,9 +6,9 @@ module ActiveAgent
|
|
6
6
|
class OpenRouterProvider < OpenAIProvider
|
7
7
|
def initialize(config)
|
8
8
|
@config = config
|
9
|
-
@
|
9
|
+
@access_token ||= config["api_key"] || config["access_token"] || ENV["OPENROUTER_API_KEY"] || ENV["OPENROUTER_ACCESS_TOKEN"]
|
10
10
|
@model_name = config["model"]
|
11
|
-
@client = OpenAI::Client.new(uri_base: "https://openrouter.ai/api/v1", access_token: @
|
11
|
+
@client = OpenAI::Client.new(uri_base: "https://openrouter.ai/api/v1", access_token: @access_token, log_errors: true)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -10,6 +10,34 @@ module ActiveAgent
|
|
10
10
|
@message = message || prompt.message
|
11
11
|
@raw_response = raw_response
|
12
12
|
end
|
13
|
+
|
14
|
+
# Extract usage statistics from the raw response
|
15
|
+
def usage
|
16
|
+
return nil unless @raw_response
|
17
|
+
|
18
|
+
# OpenAI/OpenRouter format
|
19
|
+
if @raw_response.is_a?(Hash) && @raw_response["usage"]
|
20
|
+
@raw_response["usage"]
|
21
|
+
# Anthropic format
|
22
|
+
elsif @raw_response.is_a?(Hash) && @raw_response["usage"]
|
23
|
+
@raw_response["usage"]
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Helper methods for common usage stats
|
30
|
+
def prompt_tokens
|
31
|
+
usage&.dig("prompt_tokens")
|
32
|
+
end
|
33
|
+
|
34
|
+
def completion_tokens
|
35
|
+
usage&.dig("completion_tokens")
|
36
|
+
end
|
37
|
+
|
38
|
+
def total_tokens
|
39
|
+
usage&.dig("total_tokens")
|
40
|
+
end
|
13
41
|
end
|
14
42
|
end
|
15
43
|
end
|
@@ -12,17 +12,21 @@ module ActiveAgent
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
|
-
def configuration(
|
16
|
-
config = ActiveAgent.config[
|
15
|
+
def configuration(name_or_provider, **options)
|
16
|
+
config = ActiveAgent.config[name_or_provider.to_s] || ActiveAgent.config.dig(ENV["RAILS_ENV"], name_or_provider.to_s) || {}
|
17
17
|
|
18
|
-
|
18
|
+
config = { "service" => "OpenAI" } if config.empty? && name_or_provider == :openai
|
19
19
|
config.merge!(options)
|
20
|
+
raise "Failed to load provider #{name_or_provider}: configuration not found for provider" if config["service"].nil?
|
20
21
|
configure_provider(config)
|
22
|
+
rescue LoadError => e
|
23
|
+
raise RuntimeError, "Failed to load provider #{name_or_provider}: #{e.message}"
|
21
24
|
end
|
22
25
|
|
23
26
|
def configure_provider(config)
|
24
|
-
|
25
|
-
|
27
|
+
service_name = config["service"]
|
28
|
+
require "active_agent/generation_provider/#{service_name.underscore}_provider"
|
29
|
+
ActiveAgent::GenerationProvider.const_get("#{service_name.camelize}Provider").new(config)
|
26
30
|
end
|
27
31
|
|
28
32
|
def generation_provider
|
@@ -40,6 +44,9 @@ module ActiveAgent
|
|
40
44
|
when Symbol, String
|
41
45
|
provider = configuration(name_or_provider)
|
42
46
|
assign_provider(name_or_provider.to_s, provider)
|
47
|
+
when OpenAI::Client
|
48
|
+
name = :openai
|
49
|
+
assign_provider(name, name_or_provider)
|
43
50
|
else
|
44
51
|
raise ArgumentError
|
45
52
|
end
|
data/lib/active_agent/version.rb
CHANGED
data/lib/active_agent.rb
CHANGED
data/lib/generators/USAGE
CHANGED
@@ -3,23 +3,45 @@ Description:
|
|
3
3
|
configuration files and application agent class.
|
4
4
|
|
5
5
|
This generator creates:
|
6
|
-
- Configuration file (config/active_agent.yml)
|
6
|
+
- Configuration file (config/active_agent.yml) unless --skip-config is used
|
7
7
|
- Application agent base class (app/agents/application_agent.rb)
|
8
8
|
- Layout templates for agent views (via template engine hooks)
|
9
9
|
|
10
|
+
Options:
|
11
|
+
--skip-config Skip the creation of config/active_agent.yml
|
12
|
+
--formats=FORMAT Specify formats to generate layouts for (default: text)
|
13
|
+
Can be one or more of: text, html, json
|
14
|
+
Example: --formats=text html json
|
15
|
+
|
10
16
|
Examples:
|
11
17
|
`bin/rails generate active_agent:install`
|
12
18
|
|
13
|
-
creates the basic ActiveAgent setup:
|
19
|
+
creates the basic ActiveAgent setup with default text format:
|
14
20
|
Config: config/active_agent.yml
|
15
21
|
Base Agent: app/agents/application_agent.rb
|
16
|
-
|
17
|
-
|
22
|
+
Layout: app/views/layouts/agent.text.erb
|
23
|
+
|
24
|
+
`bin/rails generate active_agent:install --formats=text html json`
|
25
|
+
|
26
|
+
creates ActiveAgent setup with all format layouts:
|
27
|
+
Config: config/active_agent.yml
|
28
|
+
Base Agent: app/agents/application_agent.rb
|
29
|
+
Layouts: app/views/layouts/agent.text.erb
|
30
|
+
app/views/layouts/agent.html.erb
|
18
31
|
app/views/layouts/agent.json.erb
|
19
32
|
|
20
|
-
`bin/rails generate active_agent:install --
|
33
|
+
`bin/rails generate active_agent:install --skip-config`
|
21
34
|
|
22
|
-
creates ActiveAgent setup
|
35
|
+
creates ActiveAgent setup without configuration file:
|
36
|
+
Base Agent: app/agents/application_agent.rb
|
37
|
+
Layout: app/views/layouts/agent.text.erb
|
38
|
+
|
39
|
+
`bin/rails generate active_agent:install --skip-config --formats=html json`
|
40
|
+
|
41
|
+
creates ActiveAgent setup without config file but with HTML and JSON layouts:
|
42
|
+
Base Agent: app/agents/application_agent.rb
|
43
|
+
Layouts: app/views/layouts/agent.html.erb
|
44
|
+
app/views/layouts/agent.json.erb
|
23
45
|
|
24
46
|
After running this generator, you can create individual agents with:
|
25
47
|
`bin/rails generate active_agent:agent AGENT_NAME ACTION_NAME`
|
@@ -6,25 +6,51 @@ Description:
|
|
6
6
|
engine and test framework generators. If no ApplicationAgent exists,
|
7
7
|
it will be created automatically.
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
-
|
12
|
-
-
|
9
|
+
By default, only text format views are created. You can specify which
|
10
|
+
formats to generate using the --formats option:
|
11
|
+
- text (.text.erb) - for plain text responses (default)
|
12
|
+
- html (.html.erb) - for rich text responses
|
13
|
+
- json (.json.erb) - for structured function calling responses
|
14
|
+
|
15
|
+
Options:
|
16
|
+
--formats=FORMAT Specify formats to generate views for (default: text)
|
17
|
+
Can be one or more of: text, html, json
|
18
|
+
Example: --formats=text html json
|
13
19
|
|
14
20
|
Examples:
|
15
21
|
`bin/rails generate active_agent:agent inventory search`
|
16
22
|
|
17
|
-
creates an inventory agent class
|
23
|
+
creates an inventory agent class with text format view and test:
|
24
|
+
Agent: app/agents/inventory_agent.rb
|
25
|
+
View: app/views/inventory_agent/search.text.erb
|
26
|
+
Test: test/agents/inventory_agent_test.rb
|
27
|
+
|
28
|
+
`bin/rails generate active_agent:agent inventory search --formats=html json`
|
29
|
+
|
30
|
+
creates an inventory agent with html and json views:
|
18
31
|
Agent: app/agents/inventory_agent.rb
|
19
32
|
Views: app/views/inventory_agent/search.html.erb
|
20
|
-
app/views/inventory_agent/search.text.erb
|
21
33
|
app/views/inventory_agent/search.json.erb
|
22
34
|
Test: test/agents/inventory_agent_test.rb
|
23
35
|
|
24
|
-
`bin/rails generate active_agent:agent inventory search update report`
|
36
|
+
`bin/rails generate active_agent:agent inventory search update report --formats=text html json`
|
25
37
|
|
26
|
-
creates an inventory agent with search, update, and report actions
|
38
|
+
creates an inventory agent with search, update, and report actions in all formats:
|
39
|
+
Agent: app/agents/inventory_agent.rb
|
40
|
+
Views: app/views/inventory_agent/search.text.erb
|
41
|
+
app/views/inventory_agent/search.html.erb
|
42
|
+
app/views/inventory_agent/search.json.erb
|
43
|
+
app/views/inventory_agent/update.text.erb
|
44
|
+
app/views/inventory_agent/update.html.erb
|
45
|
+
app/views/inventory_agent/update.json.erb
|
46
|
+
app/views/inventory_agent/report.text.erb
|
47
|
+
app/views/inventory_agent/report.html.erb
|
48
|
+
app/views/inventory_agent/report.json.erb
|
49
|
+
Test: test/agents/inventory_agent_test.rb
|
27
50
|
|
28
|
-
`bin/rails generate active_agent:agent admin/user create --
|
51
|
+
`bin/rails generate active_agent:agent admin/user create --formats=json`
|
29
52
|
|
30
|
-
creates a namespaced admin/user agent with
|
53
|
+
creates a namespaced admin/user agent with only JSON view for API responses:
|
54
|
+
Agent: app/agents/admin/user_agent.rb
|
55
|
+
View: app/views/admin/user_agent/create.json.erb
|
56
|
+
Test: test/agents/admin/user_agent_test.rb
|
@@ -4,8 +4,8 @@ module ActiveAgent
|
|
4
4
|
module Generators
|
5
5
|
class AgentGenerator < ::Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
|
-
|
8
7
|
argument :actions, type: :array, default: [], banner: "method method"
|
8
|
+
class_option :formats, type: :array, default: [ "text" ], desc: "Specify formats to generate (text, html, json)"
|
9
9
|
|
10
10
|
check_class_collision suffix: "Agent"
|
11
11
|
|
@@ -23,6 +23,10 @@ module ActiveAgent
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
+
def formats
|
27
|
+
options[:formats].map(&:to_sym)
|
28
|
+
end
|
29
|
+
|
26
30
|
def file_name # :doc:
|
27
31
|
@_file_name ||= super.sub(/_agent\z/i, "")
|
28
32
|
end
|
@@ -3,15 +3,16 @@
|
|
3
3
|
module ActiveAgent
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
class_option :skip_config, type: :boolean, default: false, desc: "Skip configuration file generation"
|
7
|
+
class_option :formats, type: :array, default: [ "text" ], desc: "Specify formats to generate (text, html, json)"
|
8
|
+
|
6
9
|
def self.usage_path
|
7
10
|
@usage_path ||= File.expand_path("../USAGE", __dir__)
|
8
11
|
end
|
9
12
|
source_root File.expand_path("templates", __dir__)
|
10
13
|
|
11
|
-
hook_for :template_engine, :test_framework
|
12
|
-
|
13
14
|
def create_configuration
|
14
|
-
template "active_agent.yml", "config/active_agent.yml"
|
15
|
+
template "active_agent.yml", "config/active_agent.yml" unless options[:skip_config]
|
15
16
|
end
|
16
17
|
|
17
18
|
def create_application_agent
|
@@ -22,7 +23,12 @@ module ActiveAgent
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
26
|
+
hook_for :template_engine
|
27
|
+
|
25
28
|
private
|
29
|
+
def formats
|
30
|
+
options[:formats].map(&:to_sym)
|
31
|
+
end
|
26
32
|
|
27
33
|
def application_agent_file_name
|
28
34
|
"app/agents/application_agent.rb"
|
@@ -7,6 +7,7 @@ module Erb # :nodoc:
|
|
7
7
|
class AgentGenerator < Base # :nodoc:
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
9
9
|
argument :actions, type: :array, default: [], banner: "method method"
|
10
|
+
class_option :formats, type: :array, default: [ "text" ], desc: "Specify formats to generate (text, html, json)"
|
10
11
|
|
11
12
|
def copy_view_files
|
12
13
|
view_base_path = File.join("app/views", class_path, file_name + "_agent")
|
@@ -30,9 +31,8 @@ module Erb # :nodoc:
|
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
33
|
-
|
34
34
|
def formats
|
35
|
-
[
|
35
|
+
options[:formats].map(&:to_sym)
|
36
36
|
end
|
37
37
|
|
38
38
|
def file_name
|
@@ -1,15 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rails/generators"
|
4
|
-
|
5
3
|
module Erb # :nodoc:
|
6
4
|
module Generators # :nodoc:
|
7
5
|
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
8
6
|
source_root File.expand_path("templates", __dir__)
|
7
|
+
class_option :formats, type: :array, default: [ "text" ], desc: "Specify formats to generate (text, html, json)"
|
9
8
|
|
10
9
|
def create_agent_layouts
|
11
|
-
|
12
|
-
|
10
|
+
if behavior == :invoke
|
11
|
+
formats.each do |format|
|
12
|
+
puts format
|
13
|
+
layout_path = File.join("app/views/layouts", filename_with_extensions("agent", format))
|
14
|
+
template filename_with_extensions(:layout, format), layout_path unless File.exist?(layout_path)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def formats
|
21
|
+
options[:formats].map(&:to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
def file_name
|
25
|
+
@_file_name ||= super.sub(/_agent\z/i, "")
|
26
|
+
end
|
27
|
+
|
28
|
+
def filename_with_extensions(name, file_format = format)
|
29
|
+
[ name, file_format, handler ].compact.join(".")
|
30
|
+
end
|
31
|
+
|
32
|
+
def handler
|
33
|
+
:erb
|
13
34
|
end
|
14
35
|
end
|
15
36
|
end
|
@@ -4,7 +4,7 @@ require "rails/generators/test_unit"
|
|
4
4
|
|
5
5
|
module TestUnit # :nodoc:
|
6
6
|
module Generators # :nodoc:
|
7
|
-
class InstallGenerator <
|
7
|
+
class InstallGenerator < Base # :nodoc:
|
8
8
|
# TestUnit install generator for ActiveAgent
|
9
9
|
# This can be used to create additional test-specific files during installation
|
10
10
|
# Currently no additional files are needed for TestUnit setup
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeagent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.0rc3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Bowen
|
@@ -190,7 +190,6 @@ files:
|
|
190
190
|
- lib/generators/active_agent/templates/application_agent.rb.tt
|
191
191
|
- lib/generators/erb/agent_generator.rb
|
192
192
|
- lib/generators/erb/install_generator.rb
|
193
|
-
- lib/generators/erb/templates/application_agent.rb.tt
|
194
193
|
- lib/generators/erb/templates/layout.html.erb.tt
|
195
194
|
- lib/generators/erb/templates/layout.json.erb.tt
|
196
195
|
- lib/generators/erb/templates/layout.text.erb.tt
|