Add Requesty as an AI integration provider#212
Conversation
Mirror the existing OpenRouter provider to add Requesty (https://router.requesty.ai/v1), an OpenAI-compatible LLM router. Adds a dedicated Requesty provider class (auto-registered via the providers directory), the OpenAI-compatible wiring in the AI base service, docs, and specs mirroring the OpenRouter provider. Signed-off-by: Thibault Jaigu <thibault.jaigu@gmail.com>
| end | ||
|
|
||
| # Register with the integrations registry | ||
| Integrations::Registry.register(:ai, :requesty, Integrations::Providers::Requesty) |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [82/80]
| label: 'Max Tokens', | ||
| help: 'Maximum number of tokens in generated responses' | ||
|
|
||
| def validate_connection |
There was a problem hiding this comment.
Metrics/AbcSize: Assignment Branch Condition size for validate_connection is too high. [18.92/15]
Metrics/MethodLength: Method has too many lines. [19/10]
| class Requesty < Base | ||
| self.category = :ai | ||
| self.display_name = 'Requesty' | ||
| self.description = 'Access 400+ AI models from multiple providers through a single OpenAI-compatible API' |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [111/80]
| # Requesty exposes an OpenAI-compatible API, so we point RubyLLM's | ||
| # OpenAI client at the Requesty router base URL. | ||
| config.openai_api_key = @integration.credential(:api_key) | ||
| config.openai_api_base = Integrations::Providers::Requesty::API_BASE_URL |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [84/80]
| category { 'ai' } | ||
| provider { 'requesty' } | ||
| credentials { { 'api_key' => 'sk-rq-...2345' } } | ||
| settings { { 'default_model' => 'openai/gpt-4o-mini', 'max_tokens' => 4096 } } |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [84/80]
| end | ||
|
|
||
| it 'defaults to openai/gpt-4o-mini' do | ||
| expect(described_class.default_for(:default_model)).to eq('openai/gpt-4o-mini') |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [87/80]
| options = field[:options] | ||
| options.each do |label, value| | ||
| expect(value).to match(%r{^[a-z-]+/[a-z0-9.-]+$}i), | ||
| "Expected '#{value}' to be in provider/model format" |
There was a problem hiding this comment.
Layout/AlignParameters: Align the parameters of a method call if they span more than one line.
|
|
||
| it 'has options in provider/model format' do | ||
| options = field[:options] | ||
| options.each do |label, value| |
There was a problem hiding this comment.
Lint/UnusedBlockArgument: Unused block argument - label. If it's necessary, use _ or _label as an argument name to indicate that it won't be used.
| end | ||
| end | ||
|
|
||
| describe 'setting fields' do |
There was a problem hiding this comment.
Metrics/BlockLength: Block has too many lines. [36/25]
| end | ||
|
|
||
| it 'has help text for api_key' do | ||
| expect(described_class.credential_fields[:api_key][:help]).to include('requesty.ai') |
There was a problem hiding this comment.
Metrics/LineLength: Line is too long. [90/80]
There was a problem hiding this comment.
Code Review
This pull request introduces a new Requesty integration provider to allow unified access to various AI models through an OpenAI-compatible API. It includes the service configuration, documentation, factory traits, and comprehensive unit tests. The review feedback highlights a critical issue regarding global configuration state pollution in RubyLLM.configure that could leak settings across requests in a multi-tenant environment, and suggests refactoring the connection validation to improve error handling and remove redundant rescue blocks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # Requesty exposes an OpenAI-compatible API, so we point RubyLLM's | ||
| # OpenAI client at the Requesty router base URL. | ||
| config.openai_api_key = @integration.credential(:api_key) | ||
| config.openai_api_base = Integrations::Providers::Requesty::API_BASE_URL |
There was a problem hiding this comment.
Because RubyLLM.configure modifies global configuration state, setting config.openai_api_base here will persist across requests. In a multi-tenant environment, subsequent requests that use the standard openai provider or fall back to environment variables will incorrectly route their requests to Requesty because openai_api_base is never reset.
To prevent this cross-request configuration pollution, config.openai_api_base should be explicitly reset to nil (or its default) when configuring other providers or at the start of the configure_ruby_llm! block, for example:
def configure_ruby_llm!
RubyLLM.configure do |config|
# Reset any provider-specific base URLs to prevent cross-request pollution
config.openai_api_base = nil
# ...| def validate_connection | ||
| unless credentials_valid? | ||
| errors.add(:base, 'API key is required') | ||
| return false | ||
| end | ||
|
|
||
| # Validate by fetching the models list from Requesty | ||
| response = Faraday.get("#{API_BASE_URL}/models") do |req| | ||
| req.headers['Authorization'] = "Bearer #{credential(:api_key)}" | ||
| req.options.timeout = 10 | ||
| end | ||
|
|
||
| if response.status == 401 | ||
| errors.add(:base, 'Invalid API key') | ||
| return false | ||
| end | ||
|
|
||
| response.success? | ||
| rescue Faraday::Error => e | ||
| errors.add(:base, "Connection failed: #{e.message}") | ||
| false | ||
| rescue StandardError => e | ||
| errors.add(:base, "Connection failed: #{e.message}") | ||
| false | ||
| end |
There was a problem hiding this comment.
Improve the connection validation by:
- Ensuring that any non-success response (e.g., 403, 429, 500) adds a descriptive error message to
errorsinstead of failing silently with an empty error message. - Removing the redundant
rescue Faraday::Errorblock, asFaraday::Erroris a subclass ofStandardErrorand is already caught by the subsequent block.
def validate_connection
unless credentials_valid?
errors.add(:base, 'API key is required')
return false
end
# Validate by fetching the models list from Requesty
response = Faraday.get("#{API_BASE_URL}/models") do |req|
req.headers['Authorization'] = "Bearer #{credential(:api_key)}"
req.options.timeout = 10
end
if response.success?
true
else
error_message = response.status == 401 ? 'Invalid API key' : "API returned status #{response.status}"
errors.add(:base, error_message)
false
end
rescue StandardError => e
errors.add(:base, "Connection failed: #{e.message}")
false
end
This adds Requesty as an AI provider, mirroring the existing OpenRouter provider.
Requesty (
https://router.requesty.ai/v1) is an OpenAI-compatible LLM router.Changes:
app/services/integrations/providers/requesty.rb: newRequesty < Baseprovider (auto-registers via the providers-dir glob),API_BASE_URL = https://router.requesty.ai/v1, verified model list,validate_connectionhitting/models.app/services/ai/base_service.rb: OpenAI-compatible RubyLLM wiring forrequesty(base URL + key), mirroring how OpenRouter is configured.Model naming is
provider/model. Verified:ruby -con the new files passes.I work at Requesty. This mirrors the existing OpenRouter provider as closely as possible. Happy to adjust or close it if it's not a fit.