ruby_llm_community 0.0.1 → 0.0.2
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/LICENSE +22 -0
- data/README.md +172 -0
- data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +108 -0
- data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +15 -0
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +14 -0
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +6 -0
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install_generator.rb +121 -0
- data/lib/ruby_llm/active_record/acts_as.rb +382 -0
- data/lib/ruby_llm/aliases.json +217 -0
- data/lib/ruby_llm/aliases.rb +56 -0
- data/lib/ruby_llm/attachment.rb +164 -0
- data/lib/ruby_llm/chat.rb +219 -0
- data/lib/ruby_llm/chunk.rb +6 -0
- data/lib/ruby_llm/configuration.rb +75 -0
- data/lib/ruby_llm/connection.rb +126 -0
- data/lib/ruby_llm/content.rb +52 -0
- data/lib/ruby_llm/context.rb +29 -0
- data/lib/ruby_llm/embedding.rb +30 -0
- data/lib/ruby_llm/error.rb +84 -0
- data/lib/ruby_llm/image.rb +53 -0
- data/lib/ruby_llm/message.rb +76 -0
- data/lib/ruby_llm/mime_type.rb +67 -0
- data/lib/ruby_llm/model/info.rb +101 -0
- data/lib/ruby_llm/model/modalities.rb +22 -0
- data/lib/ruby_llm/model/pricing.rb +51 -0
- data/lib/ruby_llm/model/pricing_category.rb +48 -0
- data/lib/ruby_llm/model/pricing_tier.rb +34 -0
- data/lib/ruby_llm/model.rb +7 -0
- data/lib/ruby_llm/models.json +29924 -0
- data/lib/ruby_llm/models.rb +218 -0
- data/lib/ruby_llm/models_schema.json +168 -0
- data/lib/ruby_llm/provider.rb +219 -0
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +179 -0
- data/lib/ruby_llm/providers/anthropic/chat.rb +106 -0
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
- data/lib/ruby_llm/providers/anthropic/media.rb +92 -0
- data/lib/ruby_llm/providers/anthropic/models.rb +48 -0
- data/lib/ruby_llm/providers/anthropic/streaming.rb +43 -0
- data/lib/ruby_llm/providers/anthropic/tools.rb +108 -0
- data/lib/ruby_llm/providers/anthropic.rb +37 -0
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +65 -0
- data/lib/ruby_llm/providers/bedrock/media.rb +61 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +82 -0
- data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +79 -0
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +90 -0
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +91 -0
- data/lib/ruby_llm/providers/bedrock/streaming.rb +36 -0
- data/lib/ruby_llm/providers/bedrock.rb +83 -0
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +131 -0
- data/lib/ruby_llm/providers/deepseek/chat.rb +17 -0
- data/lib/ruby_llm/providers/deepseek.rb +30 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +351 -0
- data/lib/ruby_llm/providers/gemini/chat.rb +139 -0
- data/lib/ruby_llm/providers/gemini/embeddings.rb +39 -0
- data/lib/ruby_llm/providers/gemini/images.rb +48 -0
- data/lib/ruby_llm/providers/gemini/media.rb +55 -0
- data/lib/ruby_llm/providers/gemini/models.rb +41 -0
- data/lib/ruby_llm/providers/gemini/streaming.rb +58 -0
- data/lib/ruby_llm/providers/gemini/tools.rb +82 -0
- data/lib/ruby_llm/providers/gemini.rb +36 -0
- data/lib/ruby_llm/providers/gpustack/chat.rb +17 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +55 -0
- data/lib/ruby_llm/providers/gpustack.rb +33 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +163 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +26 -0
- data/lib/ruby_llm/providers/mistral/embeddings.rb +36 -0
- data/lib/ruby_llm/providers/mistral/models.rb +49 -0
- data/lib/ruby_llm/providers/mistral.rb +32 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
- data/lib/ruby_llm/providers/ollama/media.rb +50 -0
- data/lib/ruby_llm/providers/ollama.rb +29 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +306 -0
- data/lib/ruby_llm/providers/openai/chat.rb +86 -0
- data/lib/ruby_llm/providers/openai/embeddings.rb +36 -0
- data/lib/ruby_llm/providers/openai/images.rb +38 -0
- data/lib/ruby_llm/providers/openai/media.rb +81 -0
- data/lib/ruby_llm/providers/openai/models.rb +39 -0
- data/lib/ruby_llm/providers/openai/response.rb +115 -0
- data/lib/ruby_llm/providers/openai/response_media.rb +76 -0
- data/lib/ruby_llm/providers/openai/streaming.rb +190 -0
- data/lib/ruby_llm/providers/openai/tools.rb +100 -0
- data/lib/ruby_llm/providers/openai.rb +44 -0
- data/lib/ruby_llm/providers/openai_base.rb +44 -0
- data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
- data/lib/ruby_llm/providers/openrouter.rb +26 -0
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +138 -0
- data/lib/ruby_llm/providers/perplexity/chat.rb +17 -0
- data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
- data/lib/ruby_llm/providers/perplexity.rb +52 -0
- data/lib/ruby_llm/railtie.rb +17 -0
- data/lib/ruby_llm/stream_accumulator.rb +97 -0
- data/lib/ruby_llm/streaming.rb +162 -0
- data/lib/ruby_llm/tool.rb +100 -0
- data/lib/ruby_llm/tool_call.rb +31 -0
- data/lib/ruby_llm/utils.rb +49 -0
- data/lib/ruby_llm/version.rb +5 -0
- data/lib/ruby_llm.rb +98 -0
- data/lib/tasks/aliases.rake +235 -0
- data/lib/tasks/models_docs.rake +224 -0
- data/lib/tasks/models_update.rake +108 -0
- data/lib/tasks/release.rake +32 -0
- data/lib/tasks/vcr.rake +99 -0
- metadata +128 -7
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
# Custom error class that wraps API errors from different providers
|
5
|
+
# into a consistent format with helpful error messages.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
# begin
|
9
|
+
# chat.ask "What's 2+2?"
|
10
|
+
# rescue RubyLLM::Error => e
|
11
|
+
# puts "Couldn't chat with AI: #{e.message}"
|
12
|
+
# end
|
13
|
+
class Error < StandardError
|
14
|
+
attr_reader :response
|
15
|
+
|
16
|
+
def initialize(response = nil, message = nil)
|
17
|
+
@response = response
|
18
|
+
super(message || response&.body)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Error classes for non-HTTP errors
|
23
|
+
class ConfigurationError < StandardError; end
|
24
|
+
class InvalidRoleError < StandardError; end
|
25
|
+
class ModelNotFoundError < StandardError; end
|
26
|
+
class UnsupportedAttachmentError < StandardError; end
|
27
|
+
|
28
|
+
# Error classes for different HTTP status codes
|
29
|
+
class BadRequestError < Error; end
|
30
|
+
class ForbiddenError < Error; end
|
31
|
+
class OverloadedError < Error; end
|
32
|
+
class PaymentRequiredError < Error; end
|
33
|
+
class RateLimitError < Error; end
|
34
|
+
class ServerError < Error; end
|
35
|
+
class ServiceUnavailableError < Error; end
|
36
|
+
class UnauthorizedError < Error; end
|
37
|
+
|
38
|
+
# Faraday middleware that maps provider-specific API errors to RubyLLM errors.
|
39
|
+
# Uses provider's parse_error method to extract meaningful error messages.
|
40
|
+
class ErrorMiddleware < Faraday::Middleware
|
41
|
+
def initialize(app, options = {})
|
42
|
+
super(app)
|
43
|
+
@provider = options[:provider]
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(env)
|
47
|
+
@app.call(env).on_complete do |response|
|
48
|
+
self.class.parse_error(provider: @provider, response: response)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def parse_error(provider:, response:) # rubocop:disable Metrics/PerceivedComplexity
|
54
|
+
message = provider&.parse_error(response)
|
55
|
+
|
56
|
+
case response.status
|
57
|
+
when 200..399
|
58
|
+
message
|
59
|
+
when 400
|
60
|
+
raise BadRequestError.new(response, message || 'Invalid request - please check your input')
|
61
|
+
when 401
|
62
|
+
raise UnauthorizedError.new(response, message || 'Invalid API key - check your credentials')
|
63
|
+
when 402
|
64
|
+
raise PaymentRequiredError.new(response, message || 'Payment required - please top up your account')
|
65
|
+
when 403
|
66
|
+
raise ForbiddenError.new(response,
|
67
|
+
message || 'Forbidden - you do not have permission to access this resource')
|
68
|
+
when 429
|
69
|
+
raise RateLimitError.new(response, message || 'Rate limit exceeded - please wait a moment')
|
70
|
+
when 500
|
71
|
+
raise ServerError.new(response, message || 'API server error - please try again')
|
72
|
+
when 502..503
|
73
|
+
raise ServiceUnavailableError.new(response, message || 'API server unavailable - please try again later')
|
74
|
+
when 529
|
75
|
+
raise OverloadedError.new(response, message || 'Service overloaded - please try again later')
|
76
|
+
else
|
77
|
+
raise Error.new(response, message || 'An unknown error occurred')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Faraday::Middleware.register_middleware(llm_errors: RubyLLM::ErrorMiddleware)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
# Represents a generated image from an AI model.
|
5
|
+
# Provides an interface to image generation capabilities
|
6
|
+
# from providers like DALL-E and Gemini's Imagen.
|
7
|
+
class Image
|
8
|
+
attr_reader :url, :data, :mime_type, :revised_prompt, :model_id
|
9
|
+
|
10
|
+
def initialize(url: nil, data: nil, mime_type: nil, revised_prompt: nil, model_id: nil)
|
11
|
+
@url = url
|
12
|
+
@data = data
|
13
|
+
@mime_type = mime_type
|
14
|
+
@revised_prompt = revised_prompt
|
15
|
+
@model_id = model_id
|
16
|
+
end
|
17
|
+
|
18
|
+
def base64?
|
19
|
+
!@data.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the raw binary image data regardless of source
|
23
|
+
def to_blob
|
24
|
+
if base64?
|
25
|
+
Base64.decode64 @data
|
26
|
+
else
|
27
|
+
response = Connection.basic.get @url
|
28
|
+
response.body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Saves the image to a file path
|
33
|
+
def save(path)
|
34
|
+
File.binwrite(File.expand_path(path), to_blob)
|
35
|
+
path
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.paint(prompt, # rubocop:disable Metrics/ParameterLists
|
39
|
+
model: nil,
|
40
|
+
provider: nil,
|
41
|
+
assume_model_exists: false,
|
42
|
+
size: '1024x1024',
|
43
|
+
context: nil)
|
44
|
+
config = context&.config || RubyLLM.config
|
45
|
+
model ||= config.default_image_model
|
46
|
+
model, provider_instance = Models.resolve(model, provider: provider, assume_exists: assume_model_exists,
|
47
|
+
config: config)
|
48
|
+
model_id = model.id
|
49
|
+
|
50
|
+
provider_instance.paint(prompt, model: model_id, size:)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
# A single message in a chat conversation. Can represent user input,
|
5
|
+
# AI responses, or tool interactions. Tracks token usage and handles
|
6
|
+
# the complexities of tool calls and responses.
|
7
|
+
class Message
|
8
|
+
ROLES = %i[system user assistant tool].freeze
|
9
|
+
|
10
|
+
attr_reader :role, :tool_calls, :tool_call_id, :input_tokens, :output_tokens, :model_id, :raw
|
11
|
+
attr_writer :content
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
@role = options.fetch(:role).to_sym
|
15
|
+
@content = normalize_content(options.fetch(:content))
|
16
|
+
@tool_calls = options[:tool_calls]
|
17
|
+
@input_tokens = options[:input_tokens]
|
18
|
+
@output_tokens = options[:output_tokens]
|
19
|
+
@model_id = options[:model_id]
|
20
|
+
@tool_call_id = options[:tool_call_id]
|
21
|
+
@raw = options[:raw]
|
22
|
+
|
23
|
+
ensure_valid_role
|
24
|
+
end
|
25
|
+
|
26
|
+
def content
|
27
|
+
if @content.is_a?(Content) && @content.text && @content.attachments.empty?
|
28
|
+
@content.text
|
29
|
+
else
|
30
|
+
@content
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def tool_call?
|
35
|
+
!tool_calls.nil? && !tool_calls.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def tool_result?
|
39
|
+
!tool_call_id.nil? && !tool_call_id.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def tool_results
|
43
|
+
content if tool_result?
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_h
|
47
|
+
{
|
48
|
+
role: role,
|
49
|
+
content: content,
|
50
|
+
tool_calls: tool_calls,
|
51
|
+
tool_call_id: tool_call_id,
|
52
|
+
input_tokens: input_tokens,
|
53
|
+
output_tokens: output_tokens,
|
54
|
+
model_id: model_id
|
55
|
+
}.compact
|
56
|
+
end
|
57
|
+
|
58
|
+
def instance_variables
|
59
|
+
super - [:@raw]
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def normalize_content(content)
|
65
|
+
case content
|
66
|
+
when String then Content.new(content)
|
67
|
+
when Hash then Content.new(content[:text], content)
|
68
|
+
else content # Pass through nil, Content, or other types
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ensure_valid_role
|
73
|
+
raise InvalidRoleError, "Expected role to be one of: #{ROLES.join(', ')}" unless ROLES.include?(role)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'marcel'
|
4
|
+
|
5
|
+
module RubyLLM
|
6
|
+
# MimeTypes module provides methods to handle MIME types using Marcel gem
|
7
|
+
module MimeType
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def for(...)
|
11
|
+
Marcel::MimeType.for(...)
|
12
|
+
end
|
13
|
+
|
14
|
+
def image?(type)
|
15
|
+
type.start_with?('image/')
|
16
|
+
end
|
17
|
+
|
18
|
+
def audio?(type)
|
19
|
+
type.start_with?('audio/')
|
20
|
+
end
|
21
|
+
|
22
|
+
def pdf?(type)
|
23
|
+
type == 'application/pdf'
|
24
|
+
end
|
25
|
+
|
26
|
+
def text?(type)
|
27
|
+
type.start_with?('text/') ||
|
28
|
+
TEXT_SUFFIXES.any? { |suffix| type.end_with?(suffix) } ||
|
29
|
+
NON_TEXT_PREFIX_TEXT_MIME_TYPES.include?(type)
|
30
|
+
end
|
31
|
+
|
32
|
+
# MIME types that have a text/ prefix but need to be handled differently
|
33
|
+
TEXT_SUFFIXES = ['+json', '+xml', '+html', '+yaml', '+csv', '+plain', '+javascript', '+svg'].freeze
|
34
|
+
|
35
|
+
# MIME types that don't have a text/ prefix but should be treated as text
|
36
|
+
NON_TEXT_PREFIX_TEXT_MIME_TYPES = [
|
37
|
+
'application/json', # Base type, even if specific ones end with +json
|
38
|
+
'application/xml', # Base type, even if specific ones end with +xml
|
39
|
+
'application/javascript',
|
40
|
+
'application/ecmascript',
|
41
|
+
'application/rtf',
|
42
|
+
'application/sql',
|
43
|
+
'application/x-sh',
|
44
|
+
'application/x-csh',
|
45
|
+
'application/x-httpd-php',
|
46
|
+
'application/sdp',
|
47
|
+
'application/sparql-query',
|
48
|
+
'application/graphql',
|
49
|
+
'application/yang', # Data modeling language, often serialized as XML/JSON but the type itself is distinct
|
50
|
+
'application/mbox', # Mailbox format
|
51
|
+
'application/x-tex',
|
52
|
+
'application/x-latex',
|
53
|
+
'application/x-perl',
|
54
|
+
'application/x-python',
|
55
|
+
'application/x-tcl',
|
56
|
+
'application/pgp-signature', # Often ASCII armored
|
57
|
+
'application/pgp-keys', # Often ASCII armored
|
58
|
+
'application/vnd.coffeescript',
|
59
|
+
'application/vnd.dart',
|
60
|
+
'application/vnd.oai.openapi', # Base for OpenAPI, often with +json or +yaml suffix
|
61
|
+
'application/vnd.zul', # ZK User Interface Language (can be XML-like)
|
62
|
+
'application/x-yaml', # Common non-standard for YAML
|
63
|
+
'application/yaml', # Standard for YAML
|
64
|
+
'application/toml' # TOML configuration files
|
65
|
+
].freeze
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Model
|
5
|
+
# Information about an AI model's capabilities, pricing, and metadata.
|
6
|
+
# Used by the Models registry to help developers choose the right model
|
7
|
+
# for their needs.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
# model = RubyLLM.models.find('gpt-4')
|
11
|
+
# model.supports_vision? # => true
|
12
|
+
# model.supports_functions? # => true
|
13
|
+
# model.input_price_per_million # => 30.0
|
14
|
+
class Info
|
15
|
+
attr_reader :id, :name, :provider, :family, :created_at, :context_window, :max_output_tokens, :knowledge_cutoff,
|
16
|
+
:modalities, :capabilities, :pricing, :metadata
|
17
|
+
|
18
|
+
def initialize(data)
|
19
|
+
@id = data[:id]
|
20
|
+
@name = data[:name]
|
21
|
+
@provider = data[:provider]
|
22
|
+
@family = data[:family]
|
23
|
+
@created_at = Utils.to_time(data[:created_at])
|
24
|
+
@context_window = data[:context_window]
|
25
|
+
@max_output_tokens = data[:max_output_tokens]
|
26
|
+
@knowledge_cutoff = Utils.to_date(data[:knowledge_cutoff])
|
27
|
+
@modalities = Modalities.new(data[:modalities] || {})
|
28
|
+
@capabilities = data[:capabilities] || []
|
29
|
+
@pricing = Pricing.new(data[:pricing] || {})
|
30
|
+
@metadata = data[:metadata] || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Capability methods
|
34
|
+
def supports?(capability)
|
35
|
+
capabilities.include?(capability.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
%w[function_calling structured_output batch reasoning citations streaming].each do |cap|
|
39
|
+
define_method "#{cap}?" do
|
40
|
+
supports?(cap)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Backward compatibility methods
|
45
|
+
def display_name
|
46
|
+
name
|
47
|
+
end
|
48
|
+
|
49
|
+
def max_tokens
|
50
|
+
max_output_tokens
|
51
|
+
end
|
52
|
+
|
53
|
+
def supports_vision?
|
54
|
+
modalities.input.include?('image')
|
55
|
+
end
|
56
|
+
|
57
|
+
def supports_functions?
|
58
|
+
function_calling?
|
59
|
+
end
|
60
|
+
|
61
|
+
def input_price_per_million
|
62
|
+
pricing.text_tokens.input
|
63
|
+
end
|
64
|
+
|
65
|
+
def output_price_per_million
|
66
|
+
pricing.text_tokens.output
|
67
|
+
end
|
68
|
+
|
69
|
+
def type # rubocop:disable Metrics/PerceivedComplexity
|
70
|
+
if modalities.output.include?('embeddings') && !modalities.output.include?('text')
|
71
|
+
'embedding'
|
72
|
+
elsif modalities.output.include?('image') && !modalities.output.include?('text')
|
73
|
+
'image'
|
74
|
+
elsif modalities.output.include?('audio') && !modalities.output.include?('text')
|
75
|
+
'audio'
|
76
|
+
elsif modalities.output.include?('moderation')
|
77
|
+
'moderation'
|
78
|
+
else
|
79
|
+
'chat'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_h
|
84
|
+
{
|
85
|
+
id: id,
|
86
|
+
name: name,
|
87
|
+
provider: provider,
|
88
|
+
family: family,
|
89
|
+
created_at: created_at,
|
90
|
+
context_window: context_window,
|
91
|
+
max_output_tokens: max_output_tokens,
|
92
|
+
knowledge_cutoff: knowledge_cutoff,
|
93
|
+
modalities: modalities.to_h,
|
94
|
+
capabilities: capabilities,
|
95
|
+
pricing: pricing.to_h,
|
96
|
+
metadata: metadata
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Model
|
5
|
+
# Holds and manages input and output modalities for a language model
|
6
|
+
class Modalities
|
7
|
+
attr_reader :input, :output
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@input = Array(data[:input]).map(&:to_s)
|
11
|
+
@output = Array(data[:output]).map(&:to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
{
|
16
|
+
input: input,
|
17
|
+
output: output
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Model
|
5
|
+
# A collection that manages and provides access to different categories of pricing information
|
6
|
+
# (text tokens, images, audio tokens, embeddings)
|
7
|
+
class Pricing
|
8
|
+
def initialize(data)
|
9
|
+
@data = {}
|
10
|
+
|
11
|
+
# Initialize pricing categories
|
12
|
+
%i[text_tokens images audio_tokens embeddings].each do |category|
|
13
|
+
@data[category] = PricingCategory.new(data[category]) if data[category] && !empty_pricing?(data[category])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
if respond_to_missing?(method)
|
19
|
+
@data[method.to_sym] || PricingCategory.new
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(method, include_private = false)
|
26
|
+
%i[text_tokens images audio_tokens embeddings].include?(method.to_sym) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
@data.transform_values(&:to_h)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def empty_pricing?(data)
|
36
|
+
# Check if all pricing values in this category are zero or nil
|
37
|
+
return true unless data
|
38
|
+
|
39
|
+
%i[standard batch].each do |tier|
|
40
|
+
next unless data[tier]
|
41
|
+
|
42
|
+
data[tier].each_value do |value|
|
43
|
+
return false if value && value != 0.0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Model
|
5
|
+
# Represents pricing tiers for different usage categories (standard and batch)
|
6
|
+
class PricingCategory
|
7
|
+
attr_reader :standard, :batch
|
8
|
+
|
9
|
+
def initialize(data = {})
|
10
|
+
@standard = PricingTier.new(data[:standard] || {}) unless empty_tier?(data[:standard])
|
11
|
+
@batch = PricingTier.new(data[:batch] || {}) unless empty_tier?(data[:batch])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Shorthand methods that default to standard tier
|
15
|
+
def input
|
16
|
+
standard&.input_per_million
|
17
|
+
end
|
18
|
+
|
19
|
+
def output
|
20
|
+
standard&.output_per_million
|
21
|
+
end
|
22
|
+
|
23
|
+
def cached_input
|
24
|
+
standard&.cached_input_per_million
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get value for a specific tier
|
28
|
+
def [](key)
|
29
|
+
key == :batch ? batch : standard
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_h
|
33
|
+
result = {}
|
34
|
+
result[:standard] = standard.to_h if standard
|
35
|
+
result[:batch] = batch.to_h if batch
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def empty_tier?(tier_data)
|
42
|
+
return true unless tier_data
|
43
|
+
|
44
|
+
tier_data.values.all? { |v| v.nil? || v == 0.0 }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Model
|
5
|
+
# A dynamic class for storing non-zero pricing values with flexible attribute access
|
6
|
+
class PricingTier
|
7
|
+
def initialize(data = {})
|
8
|
+
@values = {}
|
9
|
+
|
10
|
+
# Only store non-zero values
|
11
|
+
data.each do |key, value|
|
12
|
+
@values[key.to_sym] = value if value && value != 0.0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args)
|
17
|
+
if method.to_s.end_with?('=')
|
18
|
+
key = method.to_s.chomp('=').to_sym
|
19
|
+
@values[key] = args.first if args.first && args.first != 0.0
|
20
|
+
elsif @values.key?(method)
|
21
|
+
@values[method]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(method, include_private = false)
|
26
|
+
method.to_s.end_with?('=') || @values.key?(method.to_sym) || super
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
@values
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|