Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
activeagent (0.4.3)
activeagent (0.5.0rc2)
actionpack (>= 7.2, < 9.0)
actionview (>= 7.2, < 9.0)
activejob (>= 7.2, < 9.0)
Expand Down
7 changes: 7 additions & 0 deletions lib/active_agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ module ActiveAgent
class << self
attr_accessor :config

def filter_credential_keys(example)
example.gsub(Rails.application.credentials.dig(:openai, :api_key), "<OPENAI_API_KEY>")
.gsub(Rails.application.credentials.dig(:open_router, :api_key), "<OPEN_ROUTER_API_KEY>")
end

def eager_load!
super

Expand All @@ -51,6 +56,8 @@ def load_configuration(file)
config_file = YAML.load(ERB.new(File.read(file)).result, aliases: true)
env = ENV["RAILS_ENV"] || ENV["ENV"] || "development"
@config = config_file[env] || config_file
else
@config = {}
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_agent/action_prompt/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def perform_action(action)
self.context = current_context
end

def initialize
def initialize # :nodoc:
super
@_prompt_was_called = false
@_context = ActiveAgent::ActionPrompt::Prompt.new(options: self.class.options || {})
Expand Down
12 changes: 9 additions & 3 deletions lib/active_agent/generation_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ module GenerationProvider
end

module ClassMethods
def configuration(provider_name, **options)
config = ActiveAgent.config[provider_name.to_s] || ActiveAgent.config.dig(ENV["RAILS_ENV"], provider_name.to_s)
def configuration(name_or_provider, **options)
config = ActiveAgent.config[name_or_provider.to_s] || ActiveAgent.config.dig(ENV["RAILS_ENV"], name_or_provider.to_s) || {}

raise "Configuration not found for provider: #{provider_name}" unless config
config = { "service" => "OpenAI" } if config.empty? && name_or_provider == :openai
config.merge!(options)
raise "Failed to load provider #{name_or_provider}: configuration not found for provider" if config["service"].nil?
configure_provider(config)
rescue LoadError => e
raise RuntimeError, "Failed to load provider #{name_or_provider}: #{e.message}"
end

def configure_provider(config)
Expand All @@ -40,6 +43,9 @@ def generation_provider=(name_or_provider)
when Symbol, String
provider = configuration(name_or_provider)
assign_provider(name_or_provider.to_s, provider)
when OpenAI::Client
name = :openai
assign_provider(name, name_or_provider)
else
raise ArgumentError
end
Expand Down
13 changes: 9 additions & 4 deletions lib/active_agent/generation_provider/anthropic_provider.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# lib/active_agent/generation_provider/anthropic_provider.rb

require "anthropic"
begin
gem "ruby-anthropic", "~> 0.4.2"
require "anthropic"
rescue LoadError
raise LoadError, "The 'ruby-anthropic' gem is required for AnthropicProvider. Please add it to your Gemfile and run `bundle install`."
end

require "active_agent/action_prompt/action"
require_relative "base"
require_relative "response"
Expand All @@ -10,9 +16,8 @@ module GenerationProvider
class AnthropicProvider < Base
def initialize(config)
super
@api_key = config["api_key"]
@model_name = config["model"] || "claude-3-5-sonnet-20240620"
@client = Anthropic::Client.new(access_token: @api_key)
@access_token ||= config["api_key"] || config["access_token"] || Anthropic.configuration.access_token || ENV["ANTHROPIC_ACCESS_TOKEN"]
@client = Anthropic::Client.new(access_token: @access_token)
end

def generate(prompt)
Expand Down
2 changes: 1 addition & 1 deletion lib/active_agent/generation_provider/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module ActiveAgent
module GenerationProvider
class Base
class GenerationProviderError < StandardError; end
attr_reader :client, :config, :prompt, :response
attr_reader :client, :config, :prompt, :response, :access_token, :model_name

def initialize(config)
@config = config
Expand Down
4 changes: 2 additions & 2 deletions lib/active_agent/generation_provider/ollama_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ module GenerationProvider
class OllamaProvider < OpenAIProvider
def initialize(config)
@config = config
@api_key = config["api_key"]
@access_token ||= config["api_key"] || config["access_token"] || ENV["OLLAMA_API_KEY"] || ENV["OLLAMA_ACCESS_TOKEN"]
@model_name = config["model"]
@host = config["host"] || "http://localhost:11434"
@client = OpenAI::Client.new(uri_base: @host, access_token: @api_key, log_errors: true)
@client = OpenAI::Client.new(uri_base: @host, access_token: @access_token, log_errors: true)
end
end
end
Expand Down
13 changes: 6 additions & 7 deletions lib/active_agent/generation_provider/open_ai_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ module GenerationProvider
class OpenAIProvider < Base
def initialize(config)
super
@api_key = config["api_key"]
@model_name = config["model"] || "gpt-4o-mini"
@host = config["host"] || nil
@access_token ||= config["api_key"] || config["access_token"] || OpenAI.configuration.access_token || ENV["OPENAI_ACCESS_TOKEN"]
@organization_id = config["organization_id"] || OpenAI.configuration.organization_id || ENV["OPENAI_ORGANIZATION_ID"]
@admin_token = config["admin_token"] || OpenAI.configuration.admin_token || ENV["OPENAI_ADMIN_TOKEN"]
@client = OpenAI::Client.new(access_token: @access_token, uri_base: @host, organization_id: @organization_id)

@client = if (@host = config["host"])
OpenAI::Client.new(uri_base: @host, access_token: @api_key)
else
OpenAI::Client.new(access_token: @api_key)
end
@model_name = config["model"] || "gpt-4o-mini"
end

def generate(prompt)
Expand Down
4 changes: 2 additions & 2 deletions lib/active_agent/generation_provider/open_router_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ module GenerationProvider
class OpenRouterProvider < OpenAIProvider
def initialize(config)
@config = config
@api_key = config["api_key"]
@access_token ||= config["api_key"] || config["access_token"] || ENV["OPENROUTER_API_KEY"] || ENV["OPENROUTER_ACCESS_TOKEN"]
@model_name = config["model"]
@client = OpenAI::Client.new(uri_base: "https://openrouter.ai/api/v1", access_token: @api_key, log_errors: true)
@client = OpenAI::Client.new(uri_base: "https://openrouter.ai/api/v1", access_token: @access_token, log_errors: true)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/active_agent/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ActiveAgent
VERSION = "0.4.3"
VERSION = "0.5.0rc2"
end
22 changes: 22 additions & 0 deletions test/agents/open_ai_agent_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,25 @@ class OpenAIAgentTest < ActiveSupport::TestCase
end
end
end

OpenAI.configure do |config|
config.access_token = "test-api-key"
config.organization_id = "test-organization-id"
config.log_errors = Rails.env.development?
config.request_timeout = 600
end

class OpenAIClientTest < ActiveSupport::TestCase
real_config = ActiveAgent.config
ActiveAgent.load_configuration("")
class OpenAIClientAgent < ApplicationAgent
layout "agent"
generate_with :openai
end

test "loads configuration from environment" do
client = OpenAI::Client.new
assert_equal OpenAIClientAgent.generation_provider.access_token, client.access_token
ActiveAgent.instance_variable_set(:@config, real_config)
end
end
36 changes: 11 additions & 25 deletions test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ def setup
end

def teardown
ActiveAgent.instance_variable_set(:@config, @original_config)
ENV["RAILS_ENV"] = @original_rails_env
ActiveAgent.instance_variable_set(:@config, @original_config) if @original_config
ENV["RAILS_ENV"] = "test"
ActiveAgent.load_configuration(Rails.root.join("config/active_agent.yml"))
end

test "loads configuration from active_agent.yml file" do
ActiveAgent.instance_variable_set(:@config, nil)
# Test loading from the actual dummy app configuration
config_file = Rails.root.join("config/active_agent.yml")

Expand All @@ -33,7 +35,6 @@ def teardown

test "handles missing configuration file gracefully" do
ActiveAgent.instance_variable_set(:@config, nil)

# Try to load non-existent file
non_existent_file = "/tmp/nonexistent_active_agent.yml"

Expand All @@ -42,10 +43,12 @@ def teardown
end

# Configuration should remain nil when file doesn't exist
assert_nil ActiveAgent.config
assert_equal ActiveAgent.config, {}
# Restore original configuration
end

test "processes ERB in configuration file" do
ActiveAgent.instance_variable_set(:@config, nil)
# Create a temporary config file with ERB
erb_config = <<~YAML
test:
Expand Down Expand Up @@ -75,6 +78,7 @@ def teardown
end

test "selects environment-specific configuration" do
ActiveAgent.instance_variable_set(:@config, nil)
multi_env_config = <<~YAML
development:
openai:
Expand Down Expand Up @@ -120,6 +124,7 @@ def teardown
end

test "falls back to root configuration when environment not found" do
ActiveAgent.instance_variable_set(:@config, nil)
fallback_config = <<~YAML
openai:
service: "OpenAI"
Expand All @@ -146,27 +151,7 @@ def teardown
assert_equal "fallback-key", ActiveAgent.config["openai"]["api_key"]

temp_file.unlink
end

test "provider configuration raises error when not found" do
# Set up a configuration without the requested provider
limited_config = {
"test" => {
"anthropic" => {
"service" => "Anthropic",
"api_key" => "test-key"
}
}
}

ActiveAgent.instance_variable_set(:@config, limited_config)
ENV["RAILS_ENV"] = "test"

error = assert_raises(RuntimeError) do
ApplicationAgent.configuration(:openai)
end

assert_includes error.message, "Configuration not found for provider: openai"
# Restore original configuration
end

test "provider configuration merges options" do
Expand Down Expand Up @@ -195,6 +180,7 @@ def teardown
end

test "configuration file structure matches expected format" do
ActiveAgent.instance_variable_set(:@config, nil)
# Verify the test dummy app's configuration file has the expected structure
config_file = Rails.root.join("config/active_agent.yml")
assert File.exist?(config_file)
Expand Down
2 changes: 1 addition & 1 deletion test/dummy/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ../../..
specs:
activeagent (0.4.3)
activeagent (0.5.0rc2)
actionpack (>= 7.2, < 9.0)
actionview (>= 7.2, < 9.0)
activejob (>= 7.2, < 9.0)
Expand Down
11 changes: 7 additions & 4 deletions test/dummy/config/active_agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
# in your agent's `generate_with` config.
openai: &openai
service: "OpenAI"
api_key: <%= Rails.application.credentials.dig(:openai, :api_key) %>
access_token: <%= Rails.application.credentials.dig(:openai, :access_token) %>
open_router: &open_router
service: "OpenRouter"
api_key: <%= Rails.application.credentials.dig(:open_router, :api_key) %>
access_token: <%= Rails.application.credentials.dig(:open_router, :access_token) %>
ollama: &ollama
service: "Ollama"
api_key: ""
access_token: ""
host: "http://localhost:11434"
model: "gemma3:latest"
temperature: 0.7
Expand All @@ -36,4 +36,7 @@ test:
model: "qwen/qwen3-30b-a3b:free"
temperature: 0.7
ollama:
<<: *ollama
<<: *ollama
anthropic:
service: "Anthropic"
access_token: <%= Rails.application.credentials.dig(:anthropic, :access_token) %>
14 changes: 13 additions & 1 deletion test/dummy/config/application.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
require_relative "boot"

require "rails/all"
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Expand Down
2 changes: 1 addition & 1 deletion test/dummy/config/credentials.yml.enc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CDFxcODwfko0IbsvNh02j4PPl0hBmn717+mscjO7A0WtOyNA+YP8n01PAzuPIWaJCOVhJdXAEkNc2HmHR1WEHL2TM16G3Qd5YFceQuHzZN2NB9Tl3Ryx5ccK5jOOH485CIsf55EJfTFps+zUD6b07lIGyV3TpJkEolvuiHd4zLoKWucclmDe69g21Jxp8bpZJm8KubrgHDMivlXMp4yGym81i0gwznQg4meGxsyRqeBgAkLs2lCv0ZDeNxuvN6JnQcyOiGBAfyF7eVHg6rja6IXno44oB9kRtpnVqIVXxKACHhjJxCgv7AKv+f3BGO0YftxZY/nz7o4UFm/89+u9GnhKszDUZoRpvYoKnebn4q7p11gEgx2a6Mb9Gh+LamfkBeKTfEDfmwgspxel//BDIeT8mtQeX1Es9T9WIijrsPErV+UqHmO8bXdanuVhnmudZiJFbVlwXpm69V6JAqAJ96tCkUxfx5lN5GIroVBParVlmARV7dnlZjuKtpsL60gafTc+F/6NVt3eKvsTysnpG4crQvh9fuh5V4vujP8zZ7lWNpKAuWAeI6AorSD7R6IouvKqLjNqd6ljhfGnzVJmJlVxpzSKQ0bWSVnsoGZplfm9xWPnZB74serdwfFlnD6kUGGsmRZ4ZC8A7LH3TOAhz2iaVxDvN5im3K0CNr2x3+3GGarjhLg7QqaPwXVPULJatEoTk+oqyluDdp6+y43BEmwA2mMuZCggX8I=--5TN8EC6i07FodlKZ--79Eh4p6gDOCANQRrKF96FQ==
7RXYXaZ+9Xqohw1oMp++FYX/bKiNI9tBusC5oBNAPdj12WYZmBn+d4GZk1whE0gqwJjscVDk0dJgIXt3sZlqDHIe7pd8n4EQsnZ1mPXk2R5QVyEPuiIfISBhx1skCgqXI0ga9HBGalGoUxQLtEO2rbCYea7YCfOnwFqLsZ9ZD8ciiL7hLY3jHafhRo7CuRYcBpzOlaZLlB574nLphtxgsL0xxAi8t7bdueLpDegAxSHmpZNzWmMkNcC7W9UmCVlyieP1jCAhFkuS5JMG3WpPYo1Ft6PsvYf8rcFJEhr8s/L75B6MFZFW/45YdHRWBron9CJTNxMIdfzY3E30Bb2zoL3juv0BRbiXC3PkkB1HT+cyxRPR8XYASVHLOLH0enFE+839OFI5edOFDtMQsPoO8BSbcnji45Xc3ISORDInOBEWtJ88vSDU2S9ufY79szgWydVBYZuOW5g8ayFGG4gWHusnivRLPfjigkL8/44tb7Fmuh2vdewNma+4c1Yvj7xFMO8K4cba0xsysdPYUaCxh1Ys6tk9NZ4Y7A1+QwRFxSSHXJtuP5JsTs5vvZxc5BEPXOxv81dwmAObmSPZBGodP2XqfRb8ludCFUW/v7g24ZZx1SVTzfk7LW6bhF6+oQMkUckeXeoJrTEZBlDUuOXbrtmd+CudZukXAz8JIIhJx2ZRFNkI/yl88Usmo3vIV1topRzm0kKTdXXg0q2/VO+uOY0rF6ZW/eCYyttrcl2KSEFLTtUneKsVJczizY4L+wqe5b+tpOUHzv/EuVbsbiXZU77S/bcbWGLbb6juJgtMSUkBzAYeY5jPwdLQ2l5Vn5yCd8e4FmHn/l3cIXjrtyvjxulUnm/PkZRb0YggC8795CL/DDpftCAZqmCKV34ypn+LMhqHh1fKYuWbnuBvX0PaIXb42u2fbCrFUvAa1KRCK6OKp7j6j7H1i9eZofpQZEhOGBYOOZaZV17LVcCOO4WuskNR6UxM/BnmJ3oTMRw+wEi+KEFVKvYWDTQeepnk6p6GjVCjbr3w2VDXtpjWb3vLWh/QMwHGpPzNltmroZZDAsEsxXXZQ+3nsp4tQyeLO8w8xE1vu1lKgcFxHaQ4IX/bXSaUTOJ0OovUEL7JjY63Sr9+f9KLTWbbgQ/QZrBwoGfwB364ylOt1Vfojqz6ATasdcgQXe38L548mmEDBrJ1idfj0mqNznzYpzB7/waLK7SG92pEF6b67uRxXG73PZfCVrRGXHKwu2g27J1+bUT26Ojpa0qzHEIEEUabufJSxNKKEAXqgv/DCkXyq18YrZx+NA==--85+CXyjvfbFJi6BM--CHQakeLJ4Z0qHE/8QWnxVQ==
33 changes: 33 additions & 0 deletions test/dummy/config/no_openai_agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This file is used to configure the Active Agent Generation Providers for different environments.
# Each provider can have its own settings, such as API keys and model configurations.
# Make sure to set the API keys in your Rails credentials for each generation provider before using them
# in your agent's `generate_with` config.
open_router: &open_router
service: "OpenRouter"
access_token: <%= Rails.application.credentials.dig(:open_router, :api_key) %>
ollama: &ollama
service: "Ollama"
access_token: ""
host: "http://localhost:11434"
model: "gemma3:latest"
temperature: 0.7

development:
open_router:
<<: *open_router
model: "qwen/qwen3-30b-a3b:free"
temperature: 0.7
ollama:
<<: *ollama
test:
open_router:
<<: *open_router
model: "qwen/qwen3-30b-a3b:free"
temperature: 0.7
ollama:
<<: *ollama
anthropic:
service: "Anthropic"
access_token: "<%= 'erb-processed-key' %>"
model: "claude-3-sonnet-20240229"
temperature: <%= 0.5 + 0.2 %>
Loading