ruby_llm 0.1.0.pre30 → 0.1.0.pre31
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/.github/workflows/{gem-push.yml → cicd.yml} +32 -4
- data/.rspec_status +27 -0
- data/lib/ruby_llm/active_record/acts_as.rb +5 -5
- data/lib/ruby_llm/chat.rb +2 -2
- data/lib/ruby_llm/configuration.rb +3 -1
- data/lib/ruby_llm/content.rb +79 -0
- data/lib/ruby_llm/embedding.rb +9 -3
- data/lib/ruby_llm/message.rb +9 -1
- data/lib/ruby_llm/models.json +14 -14
- data/lib/ruby_llm/provider.rb +39 -14
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +81 -0
- data/lib/ruby_llm/providers/anthropic/chat.rb +86 -0
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
- data/lib/ruby_llm/providers/anthropic/models.rb +48 -0
- data/lib/ruby_llm/providers/anthropic/streaming.rb +37 -0
- data/lib/ruby_llm/providers/anthropic/tools.rb +97 -0
- data/lib/ruby_llm/providers/anthropic.rb +8 -234
- data/lib/ruby_llm/providers/deepseek/capabilites.rb +101 -0
- data/lib/ruby_llm/providers/deepseek.rb +4 -2
- data/lib/ruby_llm/providers/gemini/capabilities.rb +191 -0
- data/lib/ruby_llm/providers/gemini/models.rb +20 -0
- data/lib/ruby_llm/providers/gemini.rb +5 -10
- data/lib/ruby_llm/providers/openai/capabilities.rb +191 -0
- data/lib/ruby_llm/providers/openai/chat.rb +68 -0
- data/lib/ruby_llm/providers/openai/embeddings.rb +39 -0
- data/lib/ruby_llm/providers/openai/models.rb +40 -0
- data/lib/ruby_llm/providers/openai/streaming.rb +31 -0
- data/lib/ruby_llm/providers/openai/tools.rb +69 -0
- data/lib/ruby_llm/providers/openai.rb +15 -197
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +4 -2
- data/ruby_llm.gemspec +2 -0
- metadata +48 -8
- data/.github/workflows/test.yml +0 -35
- data/lib/ruby_llm/model_capabilities/anthropic.rb +0 -79
- data/lib/ruby_llm/model_capabilities/deepseek.rb +0 -132
- data/lib/ruby_llm/model_capabilities/gemini.rb +0 -190
- data/lib/ruby_llm/model_capabilities/openai.rb +0 -189
@@ -5,17 +5,24 @@ module RubyLLM
|
|
5
5
|
# OpenAI API integration. Handles chat completion, function calling,
|
6
6
|
# and OpenAI's unique streaming format. Supports GPT-4, GPT-3.5,
|
7
7
|
# and other OpenAI models.
|
8
|
-
|
9
|
-
|
8
|
+
module OpenAI
|
9
|
+
extend Provider
|
10
|
+
extend OpenAI::Chat
|
11
|
+
extend OpenAI::Embeddings
|
12
|
+
extend OpenAI::Models
|
13
|
+
extend OpenAI::Streaming
|
14
|
+
extend OpenAI::Tools
|
10
15
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
def self.extended(base)
|
17
|
+
base.extend(Provider)
|
18
|
+
base.extend(OpenAI::Chat)
|
19
|
+
base.extend(OpenAI::Embeddings)
|
20
|
+
base.extend(OpenAI::Models)
|
21
|
+
base.extend(OpenAI::Streaming)
|
22
|
+
base.extend(OpenAI::Tools)
|
16
23
|
end
|
17
24
|
|
18
|
-
|
25
|
+
module_function
|
19
26
|
|
20
27
|
def api_base
|
21
28
|
'https://api.openai.com/v1'
|
@@ -26,195 +33,6 @@ module RubyLLM
|
|
26
33
|
'Authorization' => "Bearer #{RubyLLM.config.openai_api_key}"
|
27
34
|
}
|
28
35
|
end
|
29
|
-
|
30
|
-
def completion_url
|
31
|
-
'chat/completions'
|
32
|
-
end
|
33
|
-
|
34
|
-
def stream_url
|
35
|
-
completion_url
|
36
|
-
end
|
37
|
-
|
38
|
-
def models_url
|
39
|
-
'models'
|
40
|
-
end
|
41
|
-
|
42
|
-
def embedding_url
|
43
|
-
'embeddings'
|
44
|
-
end
|
45
|
-
|
46
|
-
def build_payload(messages, tools:, temperature:, model:, stream: false) # rubocop:disable Metrics/MethodLength
|
47
|
-
{
|
48
|
-
model: model,
|
49
|
-
messages: format_messages(messages),
|
50
|
-
temperature: temperature,
|
51
|
-
stream: stream
|
52
|
-
}.tap do |payload|
|
53
|
-
if tools.any?
|
54
|
-
payload[:tools] = tools.map { |_, tool| tool_for(tool) }
|
55
|
-
payload[:tool_choice] = 'auto'
|
56
|
-
end
|
57
|
-
payload[:stream_options] = { include_usage: true } if stream
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def format_messages(messages)
|
62
|
-
messages.map do |msg|
|
63
|
-
{
|
64
|
-
role: format_role(msg.role),
|
65
|
-
content: msg.content,
|
66
|
-
tool_calls: format_tool_calls(msg.tool_calls),
|
67
|
-
tool_call_id: msg.tool_call_id
|
68
|
-
}.compact
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def format_role(role)
|
73
|
-
case role
|
74
|
-
when :system
|
75
|
-
'developer'
|
76
|
-
else
|
77
|
-
role.to_s
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def build_embedding_payload(text, model:)
|
82
|
-
{
|
83
|
-
model: model,
|
84
|
-
input: text
|
85
|
-
}
|
86
|
-
end
|
87
|
-
|
88
|
-
def parse_embedding_response(response)
|
89
|
-
embeddings = response.body['data'].map { |d| d['embedding'] }
|
90
|
-
embeddings.size == 1 ? embeddings.first : embeddings
|
91
|
-
end
|
92
|
-
|
93
|
-
def format_tool_calls(tool_calls) # rubocop:disable Metrics/MethodLength
|
94
|
-
return nil unless tool_calls&.any?
|
95
|
-
|
96
|
-
tool_calls.map do |_, tc|
|
97
|
-
{
|
98
|
-
id: tc.id,
|
99
|
-
type: 'function',
|
100
|
-
function: {
|
101
|
-
name: tc.name,
|
102
|
-
arguments: JSON.generate(tc.arguments)
|
103
|
-
}
|
104
|
-
}
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def tool_for(tool) # rubocop:disable Metrics/MethodLength
|
109
|
-
{
|
110
|
-
type: 'function',
|
111
|
-
function: {
|
112
|
-
name: tool.name,
|
113
|
-
description: tool.description,
|
114
|
-
parameters: {
|
115
|
-
type: 'object',
|
116
|
-
properties: tool.parameters.transform_values { |param| param_schema(param) },
|
117
|
-
required: tool.parameters.select { |_, p| p.required }.keys
|
118
|
-
}
|
119
|
-
}
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
def param_schema(param)
|
124
|
-
{
|
125
|
-
type: param.type,
|
126
|
-
description: param.description
|
127
|
-
}.compact
|
128
|
-
end
|
129
|
-
|
130
|
-
def parse_completion_response(response) # rubocop:disable Metrics/MethodLength
|
131
|
-
data = response.body
|
132
|
-
return if data.empty?
|
133
|
-
|
134
|
-
message_data = data.dig('choices', 0, 'message')
|
135
|
-
return unless message_data
|
136
|
-
|
137
|
-
Message.new(
|
138
|
-
role: :assistant,
|
139
|
-
content: message_data['content'],
|
140
|
-
tool_calls: parse_tool_calls(message_data['tool_calls']),
|
141
|
-
input_tokens: data['usage']['prompt_tokens'],
|
142
|
-
output_tokens: data['usage']['completion_tokens'],
|
143
|
-
model_id: data['model']
|
144
|
-
)
|
145
|
-
end
|
146
|
-
|
147
|
-
def parse_tool_calls(tool_calls, parse_arguments: true) # rubocop:disable Metrics/MethodLength
|
148
|
-
return nil unless tool_calls&.any?
|
149
|
-
|
150
|
-
tool_calls.to_h do |tc|
|
151
|
-
[
|
152
|
-
tc['id'],
|
153
|
-
ToolCall.new(
|
154
|
-
id: tc['id'],
|
155
|
-
name: tc.dig('function', 'name'),
|
156
|
-
arguments: parse_arguments ? JSON.parse(tc.dig('function', 'arguments')) : tc.dig('function', 'arguments')
|
157
|
-
)
|
158
|
-
]
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def parse_models_response(response) # rubocop:disable Metrics/MethodLength
|
163
|
-
(response.body['data'] || []).map do |model|
|
164
|
-
model_info = begin
|
165
|
-
Models.find(model['id'])
|
166
|
-
rescue StandardError
|
167
|
-
nil
|
168
|
-
end
|
169
|
-
next unless model_info
|
170
|
-
|
171
|
-
model_info.tap do |info|
|
172
|
-
info.metadata.merge!(
|
173
|
-
object: model['object'],
|
174
|
-
owned_by: model['owned_by']
|
175
|
-
)
|
176
|
-
end
|
177
|
-
end.compact
|
178
|
-
end
|
179
|
-
|
180
|
-
def handle_stream(&block) # rubocop:disable Metrics/MethodLength
|
181
|
-
to_json_stream do |data|
|
182
|
-
block.call(
|
183
|
-
Chunk.new(
|
184
|
-
role: :assistant,
|
185
|
-
model_id: data['model'],
|
186
|
-
content: data.dig('choices', 0, 'delta', 'content'),
|
187
|
-
tool_calls: parse_tool_calls(data.dig('choices', 0, 'delta', 'tool_calls'), parse_arguments: false),
|
188
|
-
input_tokens: data.dig('usage', 'prompt_tokens'),
|
189
|
-
output_tokens: data.dig('usage', 'completion_tokens')
|
190
|
-
)
|
191
|
-
)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def parse_list_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
196
|
-
(response.body['data'] || []).map do |model|
|
197
|
-
ModelInfo.new(
|
198
|
-
id: model['id'],
|
199
|
-
created_at: model['created'] ? Time.at(model['created']) : nil,
|
200
|
-
display_name: capabilities.format_display_name(model['id']),
|
201
|
-
provider: slug,
|
202
|
-
type: capabilities.model_type(model['id']),
|
203
|
-
family: capabilities.model_family(model['id']),
|
204
|
-
metadata: {
|
205
|
-
object: model['object'],
|
206
|
-
owned_by: model['owned_by']
|
207
|
-
},
|
208
|
-
context_window: capabilities.context_window_for(model['id']),
|
209
|
-
max_tokens: capabilities.max_tokens_for(model['id']),
|
210
|
-
supports_vision: capabilities.supports_vision?(model['id']),
|
211
|
-
supports_functions: capabilities.supports_functions?(model['id']),
|
212
|
-
supports_json_mode: capabilities.supports_json_mode?(model['id']),
|
213
|
-
input_price_per_million: capabilities.input_price_for(model['id']),
|
214
|
-
output_price_per_million: capabilities.output_price_for(model['id'])
|
215
|
-
)
|
216
|
-
end
|
217
|
-
end
|
218
36
|
end
|
219
37
|
end
|
220
38
|
end
|
data/lib/ruby_llm/version.rb
CHANGED
data/lib/ruby_llm.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'base64'
|
4
|
+
require 'event_stream_parser'
|
4
5
|
require 'faraday'
|
6
|
+
require 'faraday/retry'
|
5
7
|
require 'json'
|
6
8
|
require 'logger'
|
7
|
-
require 'event_stream_parser'
|
8
9
|
require 'securerandom'
|
10
|
+
require 'zeitwerk'
|
9
11
|
|
10
12
|
loader = Zeitwerk::Loader.for_gem
|
11
13
|
loader.inflector.inflect(
|
data/ruby_llm.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'event_stream_parser', '>= 0.3.0', '< 2.0.0'
|
37
37
|
spec.add_dependency 'faraday', '>= 2.0'
|
38
38
|
spec.add_dependency 'faraday-multipart', '>= 1.0'
|
39
|
+
spec.add_dependency 'faraday-retry', '>= 2.0'
|
39
40
|
spec.add_dependency 'zeitwerk', '>= 2.6'
|
40
41
|
|
41
42
|
# Rails integration dependencies
|
@@ -56,6 +57,7 @@ Gem::Specification.new do |spec|
|
|
56
57
|
spec.add_development_dependency 'rubocop', '>= 1.0'
|
57
58
|
spec.add_development_dependency 'rubocop-rake', '>= 0.6'
|
58
59
|
spec.add_development_dependency 'simplecov', '>= 0.21'
|
60
|
+
spec.add_development_dependency 'sqlite3'
|
59
61
|
spec.add_development_dependency 'webmock', '~> 3.18'
|
60
62
|
spec.add_development_dependency 'yard', '>= 0.9'
|
61
63
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_llm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.pre31
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carmine Paolino
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: event_stream_parser
|
@@ -58,6 +58,20 @@ dependencies:
|
|
58
58
|
- - ">="
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '1.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: faraday-retry
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.0'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.0'
|
61
75
|
- !ruby/object:Gem::Dependency
|
62
76
|
name: zeitwerk
|
63
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -294,6 +308,20 @@ dependencies:
|
|
294
308
|
- - ">="
|
295
309
|
- !ruby/object:Gem::Version
|
296
310
|
version: '0.21'
|
311
|
+
- !ruby/object:Gem::Dependency
|
312
|
+
name: sqlite3
|
313
|
+
requirement: !ruby/object:Gem::Requirement
|
314
|
+
requirements:
|
315
|
+
- - ">="
|
316
|
+
- !ruby/object:Gem::Version
|
317
|
+
version: '0'
|
318
|
+
type: :development
|
319
|
+
prerelease: false
|
320
|
+
version_requirements: !ruby/object:Gem::Requirement
|
321
|
+
requirements:
|
322
|
+
- - ">="
|
323
|
+
- !ruby/object:Gem::Version
|
324
|
+
version: '0'
|
297
325
|
- !ruby/object:Gem::Dependency
|
298
326
|
name: webmock
|
299
327
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,11 +360,11 @@ executables: []
|
|
332
360
|
extensions: []
|
333
361
|
extra_rdoc_files: []
|
334
362
|
files:
|
335
|
-
- ".github/workflows/
|
336
|
-
- ".github/workflows/test.yml"
|
363
|
+
- ".github/workflows/cicd.yml"
|
337
364
|
- ".gitignore"
|
338
365
|
- ".overcommit.yml"
|
339
366
|
- ".rspec"
|
367
|
+
- ".rspec_status"
|
340
368
|
- ".rubocop.yml"
|
341
369
|
- Gemfile
|
342
370
|
- LICENSE
|
@@ -349,21 +377,33 @@ files:
|
|
349
377
|
- lib/ruby_llm/chat.rb
|
350
378
|
- lib/ruby_llm/chunk.rb
|
351
379
|
- lib/ruby_llm/configuration.rb
|
380
|
+
- lib/ruby_llm/content.rb
|
352
381
|
- lib/ruby_llm/embedding.rb
|
353
382
|
- lib/ruby_llm/error.rb
|
354
383
|
- lib/ruby_llm/message.rb
|
355
|
-
- lib/ruby_llm/model_capabilities/anthropic.rb
|
356
|
-
- lib/ruby_llm/model_capabilities/deepseek.rb
|
357
|
-
- lib/ruby_llm/model_capabilities/gemini.rb
|
358
|
-
- lib/ruby_llm/model_capabilities/openai.rb
|
359
384
|
- lib/ruby_llm/model_info.rb
|
360
385
|
- lib/ruby_llm/models.json
|
361
386
|
- lib/ruby_llm/models.rb
|
362
387
|
- lib/ruby_llm/provider.rb
|
363
388
|
- lib/ruby_llm/providers/anthropic.rb
|
389
|
+
- lib/ruby_llm/providers/anthropic/capabilities.rb
|
390
|
+
- lib/ruby_llm/providers/anthropic/chat.rb
|
391
|
+
- lib/ruby_llm/providers/anthropic/embeddings.rb
|
392
|
+
- lib/ruby_llm/providers/anthropic/models.rb
|
393
|
+
- lib/ruby_llm/providers/anthropic/streaming.rb
|
394
|
+
- lib/ruby_llm/providers/anthropic/tools.rb
|
364
395
|
- lib/ruby_llm/providers/deepseek.rb
|
396
|
+
- lib/ruby_llm/providers/deepseek/capabilites.rb
|
365
397
|
- lib/ruby_llm/providers/gemini.rb
|
398
|
+
- lib/ruby_llm/providers/gemini/capabilities.rb
|
399
|
+
- lib/ruby_llm/providers/gemini/models.rb
|
366
400
|
- lib/ruby_llm/providers/openai.rb
|
401
|
+
- lib/ruby_llm/providers/openai/capabilities.rb
|
402
|
+
- lib/ruby_llm/providers/openai/chat.rb
|
403
|
+
- lib/ruby_llm/providers/openai/embeddings.rb
|
404
|
+
- lib/ruby_llm/providers/openai/models.rb
|
405
|
+
- lib/ruby_llm/providers/openai/streaming.rb
|
406
|
+
- lib/ruby_llm/providers/openai/tools.rb
|
367
407
|
- lib/ruby_llm/railtie.rb
|
368
408
|
- lib/ruby_llm/stream_accumulator.rb
|
369
409
|
- lib/ruby_llm/tool.rb
|
data/.github/workflows/test.yml
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
name: Test
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
on:
|
6
|
-
push:
|
7
|
-
branches: [ "main" ]
|
8
|
-
pull_request:
|
9
|
-
branches: [ "main" ]
|
10
|
-
workflow_call:
|
11
|
-
|
12
|
-
jobs:
|
13
|
-
test:
|
14
|
-
runs-on: ubuntu-latest
|
15
|
-
strategy:
|
16
|
-
matrix:
|
17
|
-
ruby-version: ['3.1', '3.2', '3.3']
|
18
|
-
|
19
|
-
steps:
|
20
|
-
- uses: actions/checkout@v4
|
21
|
-
|
22
|
-
- name: Set up Ruby
|
23
|
-
uses: ruby/setup-ruby@v1
|
24
|
-
with:
|
25
|
-
ruby-version: ${{ matrix.ruby-version }}
|
26
|
-
bundler-cache: true
|
27
|
-
|
28
|
-
- name: Install dependencies
|
29
|
-
run: bundle install
|
30
|
-
|
31
|
-
- name: Check code format
|
32
|
-
run: bundle exec rubocop
|
33
|
-
|
34
|
-
# - name: Run tests
|
35
|
-
# run: bundle exec rspec
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
module ModelCapabilities
|
5
|
-
# Determines capabilities and pricing for Anthropic models
|
6
|
-
module Anthropic
|
7
|
-
module_function
|
8
|
-
|
9
|
-
def determine_context_window(model_id)
|
10
|
-
case model_id
|
11
|
-
when /claude-3/ then 200_000
|
12
|
-
else 100_000
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def determine_max_tokens(model_id)
|
17
|
-
case model_id
|
18
|
-
when /claude-3-5/ then 8_192
|
19
|
-
else 4_096
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_input_price(model_id)
|
24
|
-
PRICES.dig(model_family(model_id), :input) || default_input_price
|
25
|
-
end
|
26
|
-
|
27
|
-
def get_output_price(model_id)
|
28
|
-
PRICES.dig(model_family(model_id), :output) || default_output_price
|
29
|
-
end
|
30
|
-
|
31
|
-
def supports_vision?(model_id)
|
32
|
-
return false if model_id.match?(/claude-3-5-haiku/)
|
33
|
-
return false if model_id.match?(/claude-[12]/)
|
34
|
-
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def supports_functions?(model_id)
|
39
|
-
model_id.include?('claude-3')
|
40
|
-
end
|
41
|
-
|
42
|
-
def supports_json_mode?(model_id)
|
43
|
-
model_id.include?('claude-3')
|
44
|
-
end
|
45
|
-
|
46
|
-
def model_family(model_id)
|
47
|
-
case model_id
|
48
|
-
when /claude-3-5-sonnet/ then :claude35_sonnet
|
49
|
-
when /claude-3-5-haiku/ then :claude35_haiku
|
50
|
-
when /claude-3-opus/ then :claude3_opus
|
51
|
-
when /claude-3-sonnet/ then :claude3_sonnet
|
52
|
-
when /claude-3-haiku/ then :claude3_haiku
|
53
|
-
else :claude2
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def model_type(_)
|
58
|
-
'chat'
|
59
|
-
end
|
60
|
-
|
61
|
-
PRICES = {
|
62
|
-
claude35_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
|
63
|
-
claude35_haiku: { input: 0.80, output: 4.0 }, # $0.80/$4.00 per million tokens
|
64
|
-
claude3_opus: { input: 15.0, output: 75.0 }, # $15.00/$75.00 per million tokens
|
65
|
-
claude3_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
|
66
|
-
claude3_haiku: { input: 0.25, output: 1.25 }, # $0.25/$1.25 per million tokens
|
67
|
-
claude2: { input: 3.0, output: 15.0 } # Default pricing for Claude 2.x models
|
68
|
-
}.freeze
|
69
|
-
|
70
|
-
def default_input_price
|
71
|
-
3.0
|
72
|
-
end
|
73
|
-
|
74
|
-
def default_output_price
|
75
|
-
15.0
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
module ModelCapabilities
|
5
|
-
# Determines capabilities and pricing for DeepSeek models
|
6
|
-
module DeepSeek
|
7
|
-
module_function
|
8
|
-
|
9
|
-
# Returns the context window size for the given model
|
10
|
-
# @param model_id [String] the model identifier
|
11
|
-
# @return [Integer] the context window size in tokens
|
12
|
-
def context_window_for(model_id)
|
13
|
-
case model_id
|
14
|
-
when /deepseek-(?:chat|reasoner)/ then 64_000
|
15
|
-
else 32_768 # Sensible default
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Returns the maximum output tokens for the given model
|
20
|
-
# @param model_id [String] the model identifier
|
21
|
-
# @return [Integer] the maximum output tokens
|
22
|
-
def max_tokens_for(model_id)
|
23
|
-
case model_id
|
24
|
-
when /deepseek-(?:chat|reasoner)/ then 8_192
|
25
|
-
else 4_096 # Default if max_tokens not specified
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns the input price per million tokens for the given model
|
30
|
-
# @param model_id [String] the model identifier
|
31
|
-
# @return [Float] the price per million tokens
|
32
|
-
def input_price_for(model_id)
|
33
|
-
PRICES.dig(model_family(model_id), :input_miss) || default_input_price
|
34
|
-
end
|
35
|
-
|
36
|
-
# Returns the output price per million tokens for the given model
|
37
|
-
# @param model_id [String] the model identifier
|
38
|
-
# @return [Float] the price per million tokens
|
39
|
-
def output_price_for(model_id)
|
40
|
-
PRICES.dig(model_family(model_id), :output) || default_output_price
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns the cache hit price per million tokens for the given model
|
44
|
-
# @param model_id [String] the model identifier
|
45
|
-
# @return [Float] the price per million tokens
|
46
|
-
def cache_hit_price_for(model_id)
|
47
|
-
PRICES.dig(model_family(model_id), :input_hit) || default_cache_hit_price
|
48
|
-
end
|
49
|
-
|
50
|
-
# Determines if the model supports vision capabilities
|
51
|
-
# @param model_id [String] the model identifier
|
52
|
-
# @return [Boolean] true if the model supports vision
|
53
|
-
def supports_vision?(_model_id)
|
54
|
-
false # DeepSeek models don't currently support vision
|
55
|
-
end
|
56
|
-
|
57
|
-
# Determines if the model supports function calling
|
58
|
-
# @param model_id [String] the model identifier
|
59
|
-
# @return [Boolean] true if the model supports function calling
|
60
|
-
def supports_functions?(model_id)
|
61
|
-
model_id.match?(/deepseek-chat/) # Only deepseek-chat supports function calling
|
62
|
-
end
|
63
|
-
|
64
|
-
# Determines if the model supports JSON mode
|
65
|
-
# @param model_id [String] the model identifier
|
66
|
-
# @return [Boolean] true if the model supports JSON mode
|
67
|
-
def supports_json_mode?(model_id)
|
68
|
-
model_id.match?(/deepseek-chat/) # Only deepseek-chat supports JSON mode
|
69
|
-
end
|
70
|
-
|
71
|
-
# Formats the model ID into a display name
|
72
|
-
# @param model_id [String] the model identifier
|
73
|
-
# @return [String] the formatted display name
|
74
|
-
def format_display_name(model_id)
|
75
|
-
case model_id
|
76
|
-
when 'deepseek-chat' then 'DeepSeek V3'
|
77
|
-
when 'deepseek-reasoner' then 'DeepSeek R1'
|
78
|
-
else
|
79
|
-
model_id.split('-')
|
80
|
-
.map(&:capitalize)
|
81
|
-
.join(' ')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Returns the model type
|
86
|
-
# @param model_id [String] the model identifier
|
87
|
-
# @return [String] the model type
|
88
|
-
def model_type(_model_id)
|
89
|
-
'chat' # All DeepSeek models are chat models
|
90
|
-
end
|
91
|
-
|
92
|
-
# Returns the model family for pricing purposes
|
93
|
-
# @param model_id [String] the model identifier
|
94
|
-
# @return [String] the model family identifier
|
95
|
-
def model_family(model_id)
|
96
|
-
case model_id
|
97
|
-
when /deepseek-chat/ then :chat
|
98
|
-
when /deepseek-reasoner/ then :reasoner
|
99
|
-
else :chat # Default to chat family
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Pricing information for DeepSeek models (USD per 1M tokens)
|
104
|
-
PRICES = {
|
105
|
-
chat: {
|
106
|
-
input_hit: 0.07, # $0.07 per million tokens on cache hit
|
107
|
-
input_miss: 0.27, # $0.27 per million tokens on cache miss
|
108
|
-
output: 1.10 # $1.10 per million tokens output
|
109
|
-
},
|
110
|
-
reasoner: {
|
111
|
-
input_hit: 0.14, # $0.14 per million tokens on cache hit
|
112
|
-
input_miss: 0.55, # $0.55 per million tokens on cache miss
|
113
|
-
output: 2.19 # $2.19 per million tokens output
|
114
|
-
}
|
115
|
-
}.freeze
|
116
|
-
|
117
|
-
private
|
118
|
-
|
119
|
-
def default_input_price
|
120
|
-
0.27 # Default to chat cache miss price
|
121
|
-
end
|
122
|
-
|
123
|
-
def default_output_price
|
124
|
-
1.10 # Default to chat output price
|
125
|
-
end
|
126
|
-
|
127
|
-
def default_cache_hit_price
|
128
|
-
0.07 # Default to chat cache hit price
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|