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,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Providers
|
5
|
+
class Bedrock
|
6
|
+
# Determines capabilities and pricing for AWS Bedrock models
|
7
|
+
module Capabilities
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def context_window_for(model_id)
|
11
|
+
case model_id
|
12
|
+
when /anthropic\.claude-2/ then 100_000
|
13
|
+
else 200_000
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def max_tokens_for(_model_id)
|
18
|
+
4_096
|
19
|
+
end
|
20
|
+
|
21
|
+
def input_price_for(model_id)
|
22
|
+
PRICES.dig(model_family(model_id), :input) || default_input_price
|
23
|
+
end
|
24
|
+
|
25
|
+
def output_price_for(model_id)
|
26
|
+
PRICES.dig(model_family(model_id), :output) || default_output_price
|
27
|
+
end
|
28
|
+
|
29
|
+
def supports_chat?(model_id)
|
30
|
+
model_id.match?(/anthropic\.claude/)
|
31
|
+
end
|
32
|
+
|
33
|
+
def supports_streaming?(model_id)
|
34
|
+
model_id.match?(/anthropic\.claude/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def supports_images?(model_id)
|
38
|
+
model_id.match?(/anthropic\.claude/)
|
39
|
+
end
|
40
|
+
|
41
|
+
def supports_vision?(model_id)
|
42
|
+
model_id.match?(/anthropic\.claude/)
|
43
|
+
end
|
44
|
+
|
45
|
+
def supports_functions?(model_id)
|
46
|
+
model_id.match?(/anthropic\.claude/)
|
47
|
+
end
|
48
|
+
|
49
|
+
def supports_audio?(_model_id)
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def supports_json_mode?(model_id)
|
54
|
+
model_id.match?(/anthropic\.claude/)
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_display_name(model_id)
|
58
|
+
model_id.then { |id| humanize(id) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def model_type(_model_id)
|
62
|
+
'chat'
|
63
|
+
end
|
64
|
+
|
65
|
+
def supports_structured_output?(_model_id)
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Model family patterns for capability lookup
|
70
|
+
MODEL_FAMILIES = {
|
71
|
+
/anthropic\.claude-3-opus/ => :claude3_opus,
|
72
|
+
/anthropic\.claude-3-sonnet/ => :claude3_sonnet,
|
73
|
+
/anthropic\.claude-3-5-sonnet/ => :claude3_sonnet,
|
74
|
+
/anthropic\.claude-3-7-sonnet/ => :claude3_sonnet,
|
75
|
+
/anthropic\.claude-3-haiku/ => :claude3_haiku,
|
76
|
+
/anthropic\.claude-3-5-haiku/ => :claude3_5_haiku,
|
77
|
+
/anthropic\.claude-v2/ => :claude2,
|
78
|
+
/anthropic\.claude-instant/ => :claude_instant
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
def model_family(model_id)
|
82
|
+
MODEL_FAMILIES.find { |pattern, _family| model_id.match?(pattern) }&.last || :other
|
83
|
+
end
|
84
|
+
|
85
|
+
# Pricing information for Bedrock models (per million tokens)
|
86
|
+
PRICES = {
|
87
|
+
claude3_opus: { input: 15.0, output: 75.0 },
|
88
|
+
claude3_sonnet: { input: 3.0, output: 15.0 },
|
89
|
+
claude3_haiku: { input: 0.25, output: 1.25 },
|
90
|
+
claude3_5_haiku: { input: 0.8, output: 4.0 },
|
91
|
+
claude2: { input: 8.0, output: 24.0 },
|
92
|
+
claude_instant: { input: 0.8, output: 2.4 }
|
93
|
+
}.freeze
|
94
|
+
|
95
|
+
def default_input_price
|
96
|
+
0.1
|
97
|
+
end
|
98
|
+
|
99
|
+
def default_output_price
|
100
|
+
0.2
|
101
|
+
end
|
102
|
+
|
103
|
+
def humanize(id)
|
104
|
+
id.tr('-', ' ')
|
105
|
+
.split('.')
|
106
|
+
.last
|
107
|
+
.split
|
108
|
+
.map(&:capitalize)
|
109
|
+
.join(' ')
|
110
|
+
end
|
111
|
+
|
112
|
+
def modalities_for(model_id)
|
113
|
+
modalities = {
|
114
|
+
input: ['text'],
|
115
|
+
output: ['text']
|
116
|
+
}
|
117
|
+
|
118
|
+
if model_id.match?(/anthropic\.claude/) && supports_vision?(model_id)
|
119
|
+
modalities[:input] << 'image'
|
120
|
+
modalities[:input] << 'pdf'
|
121
|
+
end
|
122
|
+
|
123
|
+
modalities
|
124
|
+
end
|
125
|
+
|
126
|
+
def capabilities_for(model_id)
|
127
|
+
capabilities = []
|
128
|
+
|
129
|
+
capabilities << 'streaming' if model_id.match?(/anthropic\.claude/)
|
130
|
+
|
131
|
+
capabilities << 'function_calling' if supports_functions?(model_id)
|
132
|
+
|
133
|
+
capabilities << 'reasoning' if model_id.match?(/claude-3-7/)
|
134
|
+
|
135
|
+
if model_id.match?(/claude-3\.5|claude-3-7/)
|
136
|
+
capabilities << 'batch'
|
137
|
+
capabilities << 'citations'
|
138
|
+
end
|
139
|
+
|
140
|
+
capabilities
|
141
|
+
end
|
142
|
+
|
143
|
+
def pricing_for(model_id)
|
144
|
+
family = model_family(model_id)
|
145
|
+
prices = PRICES.fetch(family, { input: default_input_price, output: default_output_price })
|
146
|
+
|
147
|
+
standard_pricing = {
|
148
|
+
input_per_million: prices[:input],
|
149
|
+
output_per_million: prices[:output]
|
150
|
+
}
|
151
|
+
|
152
|
+
batch_pricing = {
|
153
|
+
input_per_million: prices[:input] * 0.5,
|
154
|
+
output_per_million: prices[:output] * 0.5
|
155
|
+
}
|
156
|
+
|
157
|
+
{
|
158
|
+
text_tokens: {
|
159
|
+
standard: standard_pricing,
|
160
|
+
batch: batch_pricing
|
161
|
+
}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Providers
|
5
|
+
class Bedrock
|
6
|
+
# Chat methods for the AWS Bedrock API implementation
|
7
|
+
module Chat
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def sync_response(connection, payload, additional_headers = {})
|
11
|
+
signature = sign_request("#{connection.connection.url_prefix}#{completion_url}", payload:)
|
12
|
+
response = connection.post completion_url, payload do |req|
|
13
|
+
req.headers.merge! build_headers(signature.headers, streaming: block_given?)
|
14
|
+
# Merge additional headers, with existing headers taking precedence
|
15
|
+
req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
|
16
|
+
end
|
17
|
+
Anthropic::Chat.parse_completion_response response
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_message(msg)
|
21
|
+
if msg.tool_call?
|
22
|
+
Anthropic::Tools.format_tool_call(msg)
|
23
|
+
elsif msg.tool_result?
|
24
|
+
Anthropic::Tools.format_tool_result(msg)
|
25
|
+
else
|
26
|
+
format_basic_message(msg)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def format_basic_message(msg)
|
31
|
+
{
|
32
|
+
role: Anthropic::Chat.convert_role(msg.role),
|
33
|
+
content: Media.format_content(msg.content)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def completion_url
|
40
|
+
"model/#{@model_id}/invoke"
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil) # rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists
|
44
|
+
# Hold model_id in instance variable for use in completion_url and stream_url
|
45
|
+
@model_id = model
|
46
|
+
|
47
|
+
system_messages, chat_messages = Anthropic::Chat.separate_messages(messages)
|
48
|
+
system_content = Anthropic::Chat.build_system_content(system_messages)
|
49
|
+
|
50
|
+
build_base_payload(chat_messages, model).tap do |payload|
|
51
|
+
Anthropic::Chat.add_optional_fields(payload, system_content:, tools:, temperature:)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_base_payload(chat_messages, model)
|
56
|
+
{
|
57
|
+
anthropic_version: 'bedrock-2023-05-31',
|
58
|
+
messages: chat_messages.map { |msg| format_message(msg) },
|
59
|
+
max_tokens: RubyLLM.models.find(model)&.max_tokens || 4096
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Providers
|
5
|
+
class Bedrock
|
6
|
+
# Media handling methods for the Bedrock API integration
|
7
|
+
# NOTE: Bedrock does not support url attachments
|
8
|
+
module Media
|
9
|
+
extend Anthropic::Media
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def format_content(content)
|
14
|
+
# Convert Hash/Array back to JSON string for API
|
15
|
+
return [Anthropic::Media.format_text(content.to_json)] if content.is_a?(Hash) || content.is_a?(Array)
|
16
|
+
return [Anthropic::Media.format_text(content)] unless content.is_a?(Content)
|
17
|
+
|
18
|
+
parts = []
|
19
|
+
parts << Anthropic::Media.format_text(content.text) if content.text
|
20
|
+
|
21
|
+
content.attachments.each do |attachment|
|
22
|
+
case attachment.type
|
23
|
+
when :image
|
24
|
+
parts << format_image(attachment)
|
25
|
+
when :pdf
|
26
|
+
parts << format_pdf(attachment)
|
27
|
+
when :text
|
28
|
+
parts << Anthropic::Media.format_text_file(attachment)
|
29
|
+
else
|
30
|
+
raise UnsupportedAttachmentError, attachment.type
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
parts
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_image(image)
|
38
|
+
{
|
39
|
+
type: 'image',
|
40
|
+
source: {
|
41
|
+
type: 'base64',
|
42
|
+
media_type: image.mime_type,
|
43
|
+
data: image.encoded
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def format_pdf(pdf)
|
49
|
+
{
|
50
|
+
type: 'document',
|
51
|
+
source: {
|
52
|
+
type: 'base64',
|
53
|
+
media_type: pdf.mime_type,
|
54
|
+
data: pdf.encoded
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLLM
|
4
|
+
module Providers
|
5
|
+
class Bedrock
|
6
|
+
# Models methods for the AWS Bedrock API implementation
|
7
|
+
module Models
|
8
|
+
def list_models
|
9
|
+
mgmt_api_base = "https://bedrock.#{@config.bedrock_region}.amazonaws.com"
|
10
|
+
full_models_url = "#{mgmt_api_base}/#{models_url}"
|
11
|
+
signature = sign_request(full_models_url, method: :get)
|
12
|
+
response = @connection.get(full_models_url) do |req|
|
13
|
+
req.headers.merge! signature.headers
|
14
|
+
end
|
15
|
+
|
16
|
+
parse_list_models_response(response, slug, capabilities)
|
17
|
+
end
|
18
|
+
|
19
|
+
module_function
|
20
|
+
|
21
|
+
def models_url
|
22
|
+
'foundation-models'
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_list_models_response(response, slug, capabilities)
|
26
|
+
models = Array(response.body['modelSummaries'])
|
27
|
+
|
28
|
+
# Filter to include only models we care about
|
29
|
+
models.select { |m| m['modelId'].include?('claude') }.map do |model_data|
|
30
|
+
model_id = model_data['modelId']
|
31
|
+
|
32
|
+
Model::Info.new(
|
33
|
+
id: model_id_with_region(model_id, model_data),
|
34
|
+
name: model_data['modelName'] || capabilities.format_display_name(model_id),
|
35
|
+
provider: slug,
|
36
|
+
family: capabilities.model_family(model_id),
|
37
|
+
created_at: nil,
|
38
|
+
context_window: capabilities.context_window_for(model_id),
|
39
|
+
max_output_tokens: capabilities.max_tokens_for(model_id),
|
40
|
+
modalities: capabilities.modalities_for(model_id),
|
41
|
+
capabilities: capabilities.capabilities_for(model_id),
|
42
|
+
pricing: capabilities.pricing_for(model_id),
|
43
|
+
metadata: {
|
44
|
+
provider_name: model_data['providerName'],
|
45
|
+
inference_types: model_data['inferenceTypesSupported'] || [],
|
46
|
+
streaming_supported: model_data['responseStreamingSupported'] || false,
|
47
|
+
input_modalities: model_data['inputModalities'] || [],
|
48
|
+
output_modalities: model_data['outputModalities'] || []
|
49
|
+
}
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Simple test-friendly method that only sets the ID
|
55
|
+
def create_model_info(model_data, slug, _capabilities)
|
56
|
+
model_id = model_data['modelId']
|
57
|
+
|
58
|
+
Model::Info.new(
|
59
|
+
id: model_id_with_region(model_id, model_data),
|
60
|
+
name: model_data['modelName'] || model_id,
|
61
|
+
provider: slug,
|
62
|
+
family: 'claude',
|
63
|
+
created_at: nil,
|
64
|
+
context_window: 200_000,
|
65
|
+
max_output_tokens: 4096,
|
66
|
+
modalities: { input: ['text'], output: ['text'] },
|
67
|
+
capabilities: [],
|
68
|
+
pricing: {},
|
69
|
+
metadata: {}
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def model_id_with_region(model_id, model_data)
|
74
|
+
return model_id unless model_data['inferenceTypesSupported']&.include?('INFERENCE_PROFILE')
|
75
|
+
return model_id if model_data['inferenceTypesSupported']&.include?('ON_DEMAND')
|
76
|
+
|
77
|
+
"us.#{model_id}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|