Skip to content
Draft
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: 2 additions & 0 deletions lib/ruby_llm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
'ruby_llm' => 'RubyLLM',
'llm' => 'LLM',
'openai' => 'OpenAI',
'azure_openai' => 'AzureOpenAI',
'api' => 'API',
'deepseek' => 'DeepSeek',
'perplexity' => 'Perplexity',
Expand Down Expand Up @@ -93,6 +94,7 @@ def logger
RubyLLM::Provider.register :openrouter, RubyLLM::Providers::OpenRouter
RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity
RubyLLM::Provider.register :vertexai, RubyLLM::Providers::VertexAI
RubyLLM::Provider.register :azure_openai, RubyLLM::Providers::AzureOpenAI

if defined?(Rails::Railtie)
require 'ruby_llm/railtie'
Expand Down
4 changes: 4 additions & 0 deletions lib/ruby_llm/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class Configuration
:gpustack_api_base,
:gpustack_api_key,
:mistral_api_key,
# Azure OpenAI Provider configuration
:azure_openai_api_base,
:azure_openai_api_version,
:azure_openai_api_key,
# Default models
:default_model,
:default_embedding_model,
Expand Down
36 changes: 36 additions & 0 deletions lib/ruby_llm/models.json
Original file line number Diff line number Diff line change
Expand Up @@ -7122,6 +7122,42 @@
"owned_by": "system"
}
},
{
"id": "azure-gpt-4o",
"name": "Azure GPT-4o",
"provider": "azure_openai",
"family": "azure_gpt4o",
"created_at": "2025-05-10 20:50:49 +0200",
"context_window": 32768,
"max_output_tokens": 16384,
"knowledge_cutoff": null,
"modalities": {
"input": [
"text",
"image"
],
"output": [
"text"
]
},
"capabilities": [
"streaming",
"function_calling",
"structured_output"
],
"pricing": {
"text_tokens": {
"standard": {
"input_per_million": 2.5,
"output_per_million": 10.0
}
}
},
"metadata": {
"object": "model",
"owned_by": "system"
}
},
{
"id": "gpt-4o-2024-05-13",
"name": "GPT-4o 20240513",
Expand Down
52 changes: 52 additions & 0 deletions lib/ruby_llm/providers/azure_openai.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

module RubyLLM
module Providers
# Azure OpenAI API integration. Derived from OpenAI integration to support
# OpenAI capabilities via Microsoft Azure endpoints.
class AzureOpenAI < OpenAI
# extend AzureOpenAI::Chat
# extend AzureOpenAI::Streaming
extend AzureOpenAI::Models

def api_base
# https://<ENDPOINT>/openai/deployments/<MODEL>/chat/completions?api-version=<APIVERSION>
"#{@config.azure_openai_api_base}/openai"
end

def completion_url
# https://<ENDPOINT>/openai/deployments/<MODEL>/chat/completions?api-version=<APIVERSION>
"deployments/#{@model_id}/chat/completions?api-version=#{@config.azure_openai_api_version}"
end

def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Metrics/ParameterLists
# Hold model_id in instance variable for use in completion_url and stream_url
# It would be way better if `model` was passed to those URL methods; but ...
@model_id = model.id.start_with?('azure-') ? model.id.delete_prefix('azure-') : model.id
super
end

def headers
{
'Authorization' => "Bearer #{@config.azure_openai_api_key}"
}.compact
end

def capabilities
OpenAI::Capabilities
end

def slug
'azure_openai'
end

def configuration_requirements
%i[azure_openai_api_key azure_openai_api_base azure_openai_api_version]
end

def local?
false
end
end
end
end
33 changes: 33 additions & 0 deletions lib/ruby_llm/providers/azure_openai/models.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module RubyLLM
module Providers
class AzureOpenAI
# Models methods of the OpenAI API integration
module Models
extend OpenAI::Models

KNOWN_MODELS = [
'gpt-4o'
].freeze

module_function

def models_url
'models?api-version=2024-10-21'
end

def parse_list_models_response(response, slug, capabilities)
# select the known models only since this list from Azure OpenAI is
# very long
response.body['data'].select! do |m|
KNOWN_MODELS.include?(m['id'])
end
# Use the OpenAI processor for the list, keeping in mind that pricing etc
# won't be correct
super
end
end
end
end
end
86 changes: 86 additions & 0 deletions lib/tasks/models_update.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

require 'dotenv/load'
require 'ruby_llm'

task default: ['models:update']

namespace :models do
desc 'Update available models from providers (API keys needed)'
task :update do
puts 'Configuring RubyLLM...'
configure_from_env

refresh_models
display_model_stats
end
end

def configure_from_env
RubyLLM.configure do |config|
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY', nil)
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
configure_bedrock(config)
configure_azure_openai(config)
config.request_timeout = 30
end
end

def configure_azure_openai(config)
config.azure_openai_api_base = ENV.fetch('AZURE_OPENAI_ENDPOINT', nil)
config.azure_openai_api_key = ENV.fetch('AZURE_OPENAI_API_KEY', nil)
config.azure_openai_api_version = ENV.fetch('AZURE_OPENAI_API_VER', nil)
end

def configure_bedrock(config)
config.bedrock_api_key = ENV.fetch('AWS_ACCESS_KEY_ID', nil)
config.bedrock_secret_key = ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)
config.bedrock_region = ENV.fetch('AWS_REGION', nil)
config.bedrock_session_token = ENV.fetch('AWS_SESSION_TOKEN', nil)
end

def refresh_models
initial_count = RubyLLM.models.all.size
puts "Refreshing models (#{initial_count} cached)..."

models = RubyLLM.models.refresh!

if models.all.empty? && initial_count.zero?
puts 'Error: Failed to fetch models.'
exit(1)
elsif models.all.size == initial_count && initial_count.positive?
puts 'Warning: Model list unchanged.'
else
puts "Saving models.json (#{models.all.size} models)"
models.save_models
end

@models = models
end

def display_model_stats
puts "\nModel count:"
provider_counts = @models.all.group_by(&:provider).transform_values(&:count)

RubyLLM::Provider.providers.each_key do |sym|
name = sym.to_s.capitalize
count = provider_counts[sym.to_s] || 0
status = status(sym)
puts " #{name}: #{count} models #{status}"
end

puts 'Refresh complete.'
end

def status(provider_sym)
if RubyLLM::Provider.providers[provider_sym].local?
' (LOCAL - SKIP)'
elsif RubyLLM::Provider.providers[provider_sym].configured?
' (OK)'
else
' (NOT CONFIGURED)'
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading