Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Agent < ApplicationRecord
].freeze

# Available providers
PROVIDERS = %w[openai anthropic ollama openrouter].freeze
PROVIDERS = %w[openai anthropic ollama openrouter requesty].freeze

# Returns the configuration as a hash for versioning
def configuration_snapshot
Expand Down
16 changes: 16 additions & 0 deletions lib/active_agent/providers/requesty/_types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

require_relative "../open_ai/chat/_types"
require_relative "options"

module ActiveAgent
module Providers
module Requesty
# ActiveModel type for casting and serializing Requesty requests.
#
# Requesty is OpenAI-compatible, so requests use the same shape as the
# OpenAI Chat API. This delegates entirely to OpenAI::Chat::RequestType.
RequestType = ActiveAgent::Providers::OpenAI::Chat::RequestType
end
end
end
39 changes: 39 additions & 0 deletions lib/active_agent/providers/requesty/options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require_relative "../open_ai/options"

module ActiveAgent
module Providers
module Requesty
# Configuration options for the Requesty provider.
#
# Extends OpenAI::Options, overriding the base URL to point at Requesty's
# OpenAI-compatible gateway and resolving the API key from REQUESTY_API_KEY.
# Requesty does not use organization or project identifiers.
#
# @example Basic configuration
# options = Options.new(api_key: ENV["REQUESTY_API_KEY"])
#
# @see https://docs.requesty.ai
# @see https://app.requesty.ai/api-keys Requesty API Keys
class Options < ActiveAgent::Providers::OpenAI::Options
# @!attribute base_url
# @return [String] API endpoint (default: "https://router.requesty.ai/v1")
attribute :base_url, :string, as: "https://router.requesty.ai/v1"

private

def resolve_api_key(kwargs)
kwargs[:api_key] ||
kwargs[:access_token] ||
ENV["REQUESTY_API_KEY"] ||
ENV["REQUESTY_ACCESS_TOKEN"]
end

# Not used as part of Requesty
def resolve_organization_id(kwargs) = nil
def resolve_project_id(kwargs) = nil
end
end
end
end
63 changes: 63 additions & 0 deletions lib/active_agent/providers/requesty_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require_relative "_base_provider"

require_gem!(:openai, __FILE__)

require_relative "open_ai_provider"
require_relative "requesty/_types"

module ActiveAgent
module Providers
# Provides access to Requesty's OpenAI-compatible LLM gateway.
#
# Extends the OpenAI provider to work with Requesty's OpenAI-compatible API,
# enabling access to multiple AI models through a single interface using the
# +provider/model+ naming convention (e.g. +openai/gpt-4o-mini+).
#
# Requesty is a plain OpenAI-compatible gateway: requests, responses and
# transforms are identical to the OpenAI Chat API, so this provider reuses
# OpenAI::Chat::RequestType and OpenAI::Chat::Transforms directly. The only
# Requesty-specific configuration is the base URL and API key, which live in
# Requesty::Options.
#
# @example Configuration in active_agent.yml
# requesty:
# service: "Requesty"
# api_key: <%= ENV["REQUESTY_API_KEY"] %>
# model: "openai/gpt-4o-mini"
#
# @see OpenAI::ChatProvider
# @see https://docs.requesty.ai
class RequestyProvider < OpenAI::ChatProvider
# @return [String]
def self.service_name
"Requesty"
end

# @return [Class]
def self.options_klass
Requesty::Options
end

# @return [ActiveModel::Type::Value]
def self.prompt_request_type
OpenAI::Chat::RequestType.new
end

# @return [ActiveModel::Type::Value]
def self.embed_request_type
OpenAI::Embedding::RequestType.new
end

protected

# @see BaseProvider#api_response_normalize
# @param api_response [OpenAI::Models::ChatCompletion]
# @return [Hash] normalized response hash
def api_response_normalize(api_response)
return api_response unless api_response

OpenAI::Chat::Transforms.gem_to_hash(api_response)
end
end
end
end
35 changes: 35 additions & 0 deletions test/providers/requesty/provider_loading_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "test_helper"

class RequestyProviderLoadingTest < ActiveSupport::TestCase
test "loads RequestyProvider via requesty_provider path" do
require "active_agent/providers/requesty_provider"

assert defined?(ActiveAgent::Providers::RequestyProvider)
assert defined?(ActiveAgent::Providers::Requesty::Options)
end

test "provider concern loads Requesty service correctly" do
# Simulate how the provider concern loads providers
service_name = "Requesty"
require "active_agent/providers/#{service_name.underscore}_provider"

remaps = ActiveAgent::Provider::PROVIDER_SERVICE_NAMES_REMAPS
remapped = Hash.new(service_name).merge!(remaps)[service_name]

assert_equal "Requesty", remapped

provider_class = ActiveAgent::Providers.const_get("#{remapped.camelize}Provider")
assert_equal ActiveAgent::Providers::RequestyProvider, provider_class
end

test "Requesty options default to the Requesty gateway and REQUESTY_API_KEY" do
require "active_agent/providers/requesty_provider"

options = ActiveAgent::Providers::Requesty::Options.new(api_key: "rqsty-test")

assert_equal "https://router.requesty.ai/v1", options.base_url
assert_equal "rqsty-test", options.api_key
end
end
Loading