ruby_llm 0.1.0.pre34 → 0.1.0.pre36
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/lib/ruby_llm/models.json +153 -23
- data/lib/ruby_llm/models.rb +1 -3
- data/lib/ruby_llm/provider.rb +1 -10
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +56 -18
- data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
- data/lib/ruby_llm/providers/anthropic.rb +8 -0
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +39 -0
- data/lib/ruby_llm/providers/deepseek.rb +8 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +69 -9
- data/lib/ruby_llm/providers/gemini/models.rb +2 -2
- data/lib/ruby_llm/providers/gemini.rb +8 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +74 -23
- data/lib/ruby_llm/providers/openai/models.rb +1 -1
- data/lib/ruby_llm/providers/openai.rb +8 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/models.rake +39 -17
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed17bc0b342342484bd1e92b57f3b88a74d77cd43daeb1a569b132188025bafd
|
4
|
+
data.tar.gz: '0970e337a393e85cff88a449e723aa7c3e3e189b1923e16cf619f95d1bf65b03'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e8b35980c57cd61e10c50b3eb97dbedbe138e2720b11940646c74807ada4c8b260aff3cbee8ed84abfa4b998d3d2747ff5f2421731fcf73ea038306774a2dc9
|
7
|
+
data.tar.gz: f0211b49713b10e00f1070fa7abb00a151ee7084e3f5e14b047be2c567afe4fcd5ade1ce662697a776470ab4e6c21e98ab1c453526e36b4ec29d6f7f6b9f6c0c
|
data/lib/ruby_llm/models.json
CHANGED
@@ -11,8 +11,8 @@
|
|
11
11
|
"supports_vision": false,
|
12
12
|
"supports_functions": false,
|
13
13
|
"supports_json_mode": false,
|
14
|
-
"input_price_per_million": 0.
|
15
|
-
"output_price_per_million": 0.
|
14
|
+
"input_price_per_million": 0.0,
|
15
|
+
"output_price_per_million": 0.0,
|
16
16
|
"metadata": {
|
17
17
|
"object": "model",
|
18
18
|
"owned_by": "google"
|
@@ -23,8 +23,8 @@
|
|
23
23
|
"created_at": "2023-08-21T18:16:55+02:00",
|
24
24
|
"display_name": "Babbage 002",
|
25
25
|
"provider": "openai",
|
26
|
-
"context_window":
|
27
|
-
"max_tokens":
|
26
|
+
"context_window": 16384,
|
27
|
+
"max_tokens": 16384,
|
28
28
|
"type": "chat",
|
29
29
|
"family": "babbage",
|
30
30
|
"supports_vision": false,
|
@@ -80,7 +80,7 @@
|
|
80
80
|
"created_at": "2023-07-11T00:00:00Z",
|
81
81
|
"display_name": "Claude 2.0",
|
82
82
|
"provider": "anthropic",
|
83
|
-
"context_window":
|
83
|
+
"context_window": 200000,
|
84
84
|
"max_tokens": 4096,
|
85
85
|
"type": "chat",
|
86
86
|
"family": "claude2",
|
@@ -96,7 +96,7 @@
|
|
96
96
|
"created_at": "2023-11-21T00:00:00Z",
|
97
97
|
"display_name": "Claude 2.1",
|
98
98
|
"provider": "anthropic",
|
99
|
-
"context_window":
|
99
|
+
"context_window": 200000,
|
100
100
|
"max_tokens": 4096,
|
101
101
|
"type": "chat",
|
102
102
|
"family": "claude2",
|
@@ -116,7 +116,7 @@
|
|
116
116
|
"max_tokens": 8192,
|
117
117
|
"type": "chat",
|
118
118
|
"family": "claude35_haiku",
|
119
|
-
"supports_vision":
|
119
|
+
"supports_vision": true,
|
120
120
|
"supports_functions": true,
|
121
121
|
"supports_json_mode": true,
|
122
122
|
"input_price_per_million": 0.8,
|
@@ -155,6 +155,22 @@
|
|
155
155
|
"output_price_per_million": 15.0,
|
156
156
|
"metadata": {}
|
157
157
|
},
|
158
|
+
{
|
159
|
+
"id": "claude-3-7-sonnet-20250219",
|
160
|
+
"created_at": "2025-02-19T00:00:00Z",
|
161
|
+
"display_name": "Claude 3.7 Sonnet",
|
162
|
+
"provider": "anthropic",
|
163
|
+
"context_window": 200000,
|
164
|
+
"max_tokens": 8192,
|
165
|
+
"type": "chat",
|
166
|
+
"family": "claude37_sonnet",
|
167
|
+
"supports_vision": true,
|
168
|
+
"supports_functions": true,
|
169
|
+
"supports_json_mode": true,
|
170
|
+
"input_price_per_million": 3.0,
|
171
|
+
"output_price_per_million": 15.0,
|
172
|
+
"metadata": {}
|
173
|
+
},
|
158
174
|
{
|
159
175
|
"id": "claude-3-haiku-20240307",
|
160
176
|
"created_at": "2024-03-07T00:00:00Z",
|
@@ -246,8 +262,8 @@
|
|
246
262
|
"created_at": "2023-08-21T18:11:41+02:00",
|
247
263
|
"display_name": "Davinci 002",
|
248
264
|
"provider": "openai",
|
249
|
-
"context_window":
|
250
|
-
"max_tokens":
|
265
|
+
"context_window": 16384,
|
266
|
+
"max_tokens": 16384,
|
251
267
|
"type": "chat",
|
252
268
|
"family": "davinci",
|
253
269
|
"supports_vision": false,
|
@@ -830,6 +846,44 @@
|
|
830
846
|
"owned_by": "google"
|
831
847
|
}
|
832
848
|
},
|
849
|
+
{
|
850
|
+
"id": "gemini-2.0-flash-lite",
|
851
|
+
"created_at": null,
|
852
|
+
"display_name": "Gemini 2.0 Flash Lite",
|
853
|
+
"provider": "gemini",
|
854
|
+
"context_window": 1048576,
|
855
|
+
"max_tokens": 8192,
|
856
|
+
"type": "chat",
|
857
|
+
"family": "gemini20_flash_lite",
|
858
|
+
"supports_vision": true,
|
859
|
+
"supports_functions": false,
|
860
|
+
"supports_json_mode": false,
|
861
|
+
"input_price_per_million": 0.075,
|
862
|
+
"output_price_per_million": 0.3,
|
863
|
+
"metadata": {
|
864
|
+
"object": "model",
|
865
|
+
"owned_by": "google"
|
866
|
+
}
|
867
|
+
},
|
868
|
+
{
|
869
|
+
"id": "gemini-2.0-flash-lite-001",
|
870
|
+
"created_at": null,
|
871
|
+
"display_name": "Gemini 2.0 Flash Lite 001",
|
872
|
+
"provider": "gemini",
|
873
|
+
"context_window": 1048576,
|
874
|
+
"max_tokens": 8192,
|
875
|
+
"type": "chat",
|
876
|
+
"family": "gemini20_flash_lite",
|
877
|
+
"supports_vision": true,
|
878
|
+
"supports_functions": false,
|
879
|
+
"supports_json_mode": false,
|
880
|
+
"input_price_per_million": 0.075,
|
881
|
+
"output_price_per_million": 0.3,
|
882
|
+
"metadata": {
|
883
|
+
"object": "model",
|
884
|
+
"owned_by": "google"
|
885
|
+
}
|
886
|
+
},
|
833
887
|
{
|
834
888
|
"id": "gemini-2.0-flash-lite-preview",
|
835
889
|
"created_at": null,
|
@@ -841,7 +895,7 @@
|
|
841
895
|
"family": "gemini20_flash_lite",
|
842
896
|
"supports_vision": true,
|
843
897
|
"supports_functions": false,
|
844
|
-
"supports_json_mode":
|
898
|
+
"supports_json_mode": false,
|
845
899
|
"input_price_per_million": 0.075,
|
846
900
|
"output_price_per_million": 0.3,
|
847
901
|
"metadata": {
|
@@ -860,7 +914,7 @@
|
|
860
914
|
"family": "gemini20_flash_lite",
|
861
915
|
"supports_vision": true,
|
862
916
|
"supports_functions": false,
|
863
|
-
"supports_json_mode":
|
917
|
+
"supports_json_mode": false,
|
864
918
|
"input_price_per_million": 0.075,
|
865
919
|
"output_price_per_million": 0.3,
|
866
920
|
"metadata": {
|
@@ -868,6 +922,44 @@
|
|
868
922
|
"owned_by": "google"
|
869
923
|
}
|
870
924
|
},
|
925
|
+
{
|
926
|
+
"id": "gemini-2.0-flash-mmgen-rev17",
|
927
|
+
"created_at": null,
|
928
|
+
"display_name": "Gemini 2.0 Flash Mmgen Rev17",
|
929
|
+
"provider": "gemini",
|
930
|
+
"context_window": 1048576,
|
931
|
+
"max_tokens": 8192,
|
932
|
+
"type": "chat",
|
933
|
+
"family": "gemini20_flash",
|
934
|
+
"supports_vision": true,
|
935
|
+
"supports_functions": true,
|
936
|
+
"supports_json_mode": true,
|
937
|
+
"input_price_per_million": 0.1,
|
938
|
+
"output_price_per_million": 0.4,
|
939
|
+
"metadata": {
|
940
|
+
"object": "model",
|
941
|
+
"owned_by": "google"
|
942
|
+
}
|
943
|
+
},
|
944
|
+
{
|
945
|
+
"id": "gemini-2.0-flash-thinking-001",
|
946
|
+
"created_at": null,
|
947
|
+
"display_name": "Gemini 2.0 Flash Thinking 001",
|
948
|
+
"provider": "gemini",
|
949
|
+
"context_window": 1048576,
|
950
|
+
"max_tokens": 8192,
|
951
|
+
"type": "chat",
|
952
|
+
"family": "gemini20_flash",
|
953
|
+
"supports_vision": true,
|
954
|
+
"supports_functions": true,
|
955
|
+
"supports_json_mode": true,
|
956
|
+
"input_price_per_million": 0.1,
|
957
|
+
"output_price_per_million": 0.4,
|
958
|
+
"metadata": {
|
959
|
+
"object": "model",
|
960
|
+
"owned_by": "google"
|
961
|
+
}
|
962
|
+
},
|
871
963
|
{
|
872
964
|
"id": "gemini-2.0-flash-thinking-exp",
|
873
965
|
"created_at": null,
|
@@ -1077,6 +1169,25 @@
|
|
1077
1169
|
"owned_by": "google"
|
1078
1170
|
}
|
1079
1171
|
},
|
1172
|
+
{
|
1173
|
+
"id": "gemma-3-27b-it",
|
1174
|
+
"created_at": null,
|
1175
|
+
"display_name": "Gemma 3 27b It",
|
1176
|
+
"provider": "gemini",
|
1177
|
+
"context_window": 32768,
|
1178
|
+
"max_tokens": 4096,
|
1179
|
+
"type": "chat",
|
1180
|
+
"family": "other",
|
1181
|
+
"supports_vision": false,
|
1182
|
+
"supports_functions": false,
|
1183
|
+
"supports_json_mode": false,
|
1184
|
+
"input_price_per_million": 0.075,
|
1185
|
+
"output_price_per_million": 0.3,
|
1186
|
+
"metadata": {
|
1187
|
+
"object": "model",
|
1188
|
+
"owned_by": "google"
|
1189
|
+
}
|
1190
|
+
},
|
1080
1191
|
{
|
1081
1192
|
"id": "gpt-3.5-turbo",
|
1082
1193
|
"created_at": "2023-02-28T19:56:42+01:00",
|
@@ -1539,7 +1650,7 @@
|
|
1539
1650
|
"display_name": "GPT-4o-Mini Realtime Preview",
|
1540
1651
|
"provider": "openai",
|
1541
1652
|
"context_window": 128000,
|
1542
|
-
"max_tokens":
|
1653
|
+
"max_tokens": 4096,
|
1543
1654
|
"type": "chat",
|
1544
1655
|
"family": "gpt4o_mini_realtime",
|
1545
1656
|
"supports_vision": true,
|
@@ -1558,7 +1669,7 @@
|
|
1558
1669
|
"display_name": "GPT-4o-Mini Realtime Preview 20241217",
|
1559
1670
|
"provider": "openai",
|
1560
1671
|
"context_window": 128000,
|
1561
|
-
"max_tokens":
|
1672
|
+
"max_tokens": 4096,
|
1562
1673
|
"type": "chat",
|
1563
1674
|
"family": "gpt4o_mini_realtime",
|
1564
1675
|
"supports_vision": true,
|
@@ -1574,10 +1685,10 @@
|
|
1574
1685
|
{
|
1575
1686
|
"id": "gpt-4o-realtime-preview",
|
1576
1687
|
"created_at": "2024-09-30T03:33:18+02:00",
|
1577
|
-
"display_name": "GPT-4o
|
1688
|
+
"display_name": "GPT-4o-Realtime Preview",
|
1578
1689
|
"provider": "openai",
|
1579
1690
|
"context_window": 128000,
|
1580
|
-
"max_tokens":
|
1691
|
+
"max_tokens": 4096,
|
1581
1692
|
"type": "chat",
|
1582
1693
|
"family": "gpt4o_realtime",
|
1583
1694
|
"supports_vision": true,
|
@@ -1593,10 +1704,10 @@
|
|
1593
1704
|
{
|
1594
1705
|
"id": "gpt-4o-realtime-preview-2024-10-01",
|
1595
1706
|
"created_at": "2024-09-24T00:49:26+02:00",
|
1596
|
-
"display_name": "GPT-4o
|
1707
|
+
"display_name": "GPT-4o-Realtime Preview 20241001",
|
1597
1708
|
"provider": "openai",
|
1598
1709
|
"context_window": 128000,
|
1599
|
-
"max_tokens":
|
1710
|
+
"max_tokens": 4096,
|
1600
1711
|
"type": "chat",
|
1601
1712
|
"family": "gpt4o_realtime",
|
1602
1713
|
"supports_vision": true,
|
@@ -1612,10 +1723,10 @@
|
|
1612
1723
|
{
|
1613
1724
|
"id": "gpt-4o-realtime-preview-2024-12-17",
|
1614
1725
|
"created_at": "2024-12-11T20:30:30+01:00",
|
1615
|
-
"display_name": "GPT-4o
|
1726
|
+
"display_name": "GPT-4o-Realtime Preview 20241217",
|
1616
1727
|
"provider": "openai",
|
1617
1728
|
"context_window": 128000,
|
1618
|
-
"max_tokens":
|
1729
|
+
"max_tokens": 4096,
|
1619
1730
|
"type": "chat",
|
1620
1731
|
"family": "gpt4o_realtime",
|
1621
1732
|
"supports_vision": true,
|
@@ -1709,7 +1820,7 @@
|
|
1709
1820
|
"created_at": "2024-09-06T20:56:48+02:00",
|
1710
1821
|
"display_name": "O1-Mini",
|
1711
1822
|
"provider": "openai",
|
1712
|
-
"context_window":
|
1823
|
+
"context_window": 128000,
|
1713
1824
|
"max_tokens": 4096,
|
1714
1825
|
"type": "chat",
|
1715
1826
|
"family": "o1_mini",
|
@@ -1728,7 +1839,7 @@
|
|
1728
1839
|
"created_at": "2024-09-06T20:56:19+02:00",
|
1729
1840
|
"display_name": "O1-Mini 20240912",
|
1730
1841
|
"provider": "openai",
|
1731
|
-
"context_window":
|
1842
|
+
"context_window": 128000,
|
1732
1843
|
"max_tokens": 65536,
|
1733
1844
|
"type": "chat",
|
1734
1845
|
"family": "o1_mini",
|
@@ -1783,7 +1894,7 @@
|
|
1783
1894
|
{
|
1784
1895
|
"id": "omni-moderation-2024-09-26",
|
1785
1896
|
"created_at": "2024-11-27T20:07:46+01:00",
|
1786
|
-
"display_name": "Omni
|
1897
|
+
"display_name": "Omni-Moderation 20240926",
|
1787
1898
|
"provider": "openai",
|
1788
1899
|
"context_window": 4096,
|
1789
1900
|
"max_tokens": 4096,
|
@@ -1802,7 +1913,7 @@
|
|
1802
1913
|
{
|
1803
1914
|
"id": "omni-moderation-latest",
|
1804
1915
|
"created_at": "2024-11-15T17:47:45+01:00",
|
1805
|
-
"display_name": "Omni
|
1916
|
+
"display_name": "Omni-Moderation Latest",
|
1806
1917
|
"provider": "openai",
|
1807
1918
|
"context_window": 4096,
|
1808
1919
|
"max_tokens": 4096,
|
@@ -2046,6 +2157,25 @@
|
|
2046
2157
|
"owned_by": "system"
|
2047
2158
|
}
|
2048
2159
|
},
|
2160
|
+
{
|
2161
|
+
"id": "veo-2.0-generate-001",
|
2162
|
+
"created_at": null,
|
2163
|
+
"display_name": "Veo 2.0 Generate 001",
|
2164
|
+
"provider": "gemini",
|
2165
|
+
"context_window": 32768,
|
2166
|
+
"max_tokens": 4096,
|
2167
|
+
"type": "chat",
|
2168
|
+
"family": "other",
|
2169
|
+
"supports_vision": false,
|
2170
|
+
"supports_functions": false,
|
2171
|
+
"supports_json_mode": false,
|
2172
|
+
"input_price_per_million": 0.075,
|
2173
|
+
"output_price_per_million": 0.3,
|
2174
|
+
"metadata": {
|
2175
|
+
"object": "model",
|
2176
|
+
"owned_by": "google"
|
2177
|
+
}
|
2178
|
+
},
|
2049
2179
|
{
|
2050
2180
|
"id": "whisper-1",
|
2051
2181
|
"created_at": "2023-02-27T22:13:04+01:00",
|
data/lib/ruby_llm/models.rb
CHANGED
data/lib/ruby_llm/provider.rb
CHANGED
@@ -23,7 +23,7 @@ module RubyLLM
|
|
23
23
|
req.headers.merge! headers
|
24
24
|
end
|
25
25
|
|
26
|
-
parse_list_models_response response
|
26
|
+
parse_list_models_response response, slug, capabilities
|
27
27
|
end
|
28
28
|
|
29
29
|
def embed(text, model:)
|
@@ -150,15 +150,6 @@ module RubyLLM
|
|
150
150
|
body.is_a?(Hash) ? body.dig('error', 'message') : body
|
151
151
|
end
|
152
152
|
|
153
|
-
def capabilities
|
154
|
-
provider_name = self.class.name.split('::').last
|
155
|
-
provider_name::Capabilities
|
156
|
-
end
|
157
|
-
|
158
|
-
def slug
|
159
|
-
self.class.name.split('::').last.downcase
|
160
|
-
end
|
161
|
-
|
162
153
|
class << self
|
163
154
|
def extended(base)
|
164
155
|
base.extend(Methods)
|
@@ -7,45 +7,74 @@ module RubyLLM
|
|
7
7
|
module Capabilities
|
8
8
|
module_function
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
# Determines the context window size for a given model
|
11
|
+
# @param model_id [String] the model identifier
|
12
|
+
# @return [Integer] the context window size in tokens
|
13
|
+
def determine_context_window(_model_id)
|
14
|
+
# All Claude 3 and 3.5 and 3.7 models have 200K token context windows
|
15
|
+
200_000
|
15
16
|
end
|
16
17
|
|
18
|
+
# Determines the maximum output tokens for a given model
|
19
|
+
# @param model_id [String] the model identifier
|
20
|
+
# @return [Integer] the maximum output tokens
|
17
21
|
def determine_max_tokens(model_id)
|
18
22
|
case model_id
|
23
|
+
when /claude-3-7-sonnet/ then 8_192 # Can be increased to 64K with extended thinking
|
19
24
|
when /claude-3-5/ then 8_192
|
20
|
-
else 4_096
|
25
|
+
else 4_096 # Claude 3 Opus and Haiku
|
21
26
|
end
|
22
27
|
end
|
23
28
|
|
29
|
+
# Gets the input price per million tokens for a given model
|
30
|
+
# @param model_id [String] the model identifier
|
31
|
+
# @return [Float] the price per million tokens for input
|
24
32
|
def get_input_price(model_id)
|
25
33
|
PRICES.dig(model_family(model_id), :input) || default_input_price
|
26
34
|
end
|
27
35
|
|
36
|
+
# Gets the output price per million tokens for a given model
|
37
|
+
# @param model_id [String] the model identifier
|
38
|
+
# @return [Float] the price per million tokens for output
|
28
39
|
def get_output_price(model_id)
|
29
40
|
PRICES.dig(model_family(model_id), :output) || default_output_price
|
30
41
|
end
|
31
42
|
|
43
|
+
# Determines if a model supports vision capabilities
|
44
|
+
# @param model_id [String] the model identifier
|
45
|
+
# @return [Boolean] true if the model supports vision
|
32
46
|
def supports_vision?(model_id)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
true
|
47
|
+
# All Claude 3, 3.5, and 3.7 models support vision
|
48
|
+
!model_id.match?(/claude-[12]/)
|
37
49
|
end
|
38
50
|
|
51
|
+
# Determines if a model supports function calling
|
52
|
+
# @param model_id [String] the model identifier
|
53
|
+
# @return [Boolean] true if the model supports functions
|
39
54
|
def supports_functions?(model_id)
|
40
|
-
model_id.
|
55
|
+
model_id.match?(/claude-3/)
|
41
56
|
end
|
42
57
|
|
58
|
+
# Determines if a model supports JSON mode
|
59
|
+
# @param model_id [String] the model identifier
|
60
|
+
# @return [Boolean] true if the model supports JSON mode
|
43
61
|
def supports_json_mode?(model_id)
|
44
|
-
model_id.
|
62
|
+
model_id.match?(/claude-3/)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Determines if a model supports extended thinking
|
66
|
+
# @param model_id [String] the model identifier
|
67
|
+
# @return [Boolean] true if the model supports extended thinking
|
68
|
+
def supports_extended_thinking?(model_id)
|
69
|
+
model_id.match?(/claude-3-7-sonnet/)
|
45
70
|
end
|
46
71
|
|
72
|
+
# Determines the model family for a given model ID
|
73
|
+
# @param model_id [String] the model identifier
|
74
|
+
# @return [Symbol] the model family identifier
|
47
75
|
def model_family(model_id)
|
48
76
|
case model_id
|
77
|
+
when /claude-3-7-sonnet/ then :claude37_sonnet
|
49
78
|
when /claude-3-5-sonnet/ then :claude35_sonnet
|
50
79
|
when /claude-3-5-haiku/ then :claude35_haiku
|
51
80
|
when /claude-3-opus/ then :claude3_opus
|
@@ -55,23 +84,32 @@ module RubyLLM
|
|
55
84
|
end
|
56
85
|
end
|
57
86
|
|
87
|
+
# Returns the model type
|
88
|
+
# @param model_id [String] the model identifier (unused but kept for API consistency)
|
89
|
+
# @return [String] the model type, always 'chat' for Anthropic models
|
58
90
|
def model_type(_)
|
59
91
|
'chat'
|
60
92
|
end
|
61
93
|
|
94
|
+
# Pricing information for Anthropic models (per million tokens)
|
62
95
|
PRICES = {
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
96
|
+
claude37_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
|
97
|
+
claude35_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
|
98
|
+
claude35_haiku: { input: 0.80, output: 4.0 }, # $0.80/$4.00 per million tokens
|
99
|
+
claude3_opus: { input: 15.0, output: 75.0 }, # $15.00/$75.00 per million tokens
|
100
|
+
claude3_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
|
101
|
+
claude3_haiku: { input: 0.25, output: 1.25 }, # $0.25/$1.25 per million tokens
|
102
|
+
claude2: { input: 3.0, output: 15.0 } # Default pricing for Claude 2.x models
|
69
103
|
}.freeze
|
70
104
|
|
105
|
+
# Default input price if model not found in PRICES
|
106
|
+
# @return [Float] default price per million tokens for input
|
71
107
|
def default_input_price
|
72
108
|
3.0
|
73
109
|
end
|
74
110
|
|
111
|
+
# Default output price if model not found in PRICES
|
112
|
+
# @return [Float] default price per million tokens for output
|
75
113
|
def default_output_price
|
76
114
|
15.0
|
77
115
|
end
|
@@ -11,7 +11,7 @@ module RubyLLM
|
|
11
11
|
'/v1/models'
|
12
12
|
end
|
13
13
|
|
14
|
-
def parse_list_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
14
|
+
def parse_list_models_response(response, slug, capabilities) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
15
15
|
(response.body['data'] || []).map do |model|
|
16
16
|
ModelInfo.new(
|
17
17
|
id: model['id'],
|
@@ -7,6 +7,9 @@ module RubyLLM
|
|
7
7
|
module Capabilities
|
8
8
|
module_function
|
9
9
|
|
10
|
+
# Returns the context window size for the given model
|
11
|
+
# @param model_id [String] the model identifier
|
12
|
+
# @return [Integer] the context window size in tokens
|
10
13
|
def context_window_for(model_id)
|
11
14
|
case model_id
|
12
15
|
when /deepseek-(?:chat|reasoner)/ then 64_000
|
@@ -14,6 +17,9 @@ module RubyLLM
|
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
20
|
+
# Returns the maximum number of tokens that can be generated
|
21
|
+
# @param model_id [String] the model identifier
|
22
|
+
# @return [Integer] the maximum number of tokens
|
17
23
|
def max_tokens_for(model_id)
|
18
24
|
case model_id
|
19
25
|
when /deepseek-(?:chat|reasoner)/ then 8_192
|
@@ -21,30 +27,51 @@ module RubyLLM
|
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
30
|
+
# Returns the price per million tokens for input (cache miss)
|
31
|
+
# @param model_id [String] the model identifier
|
32
|
+
# @return [Float] the price per million tokens in USD
|
24
33
|
def input_price_for(model_id)
|
25
34
|
PRICES.dig(model_family(model_id), :input_miss) || default_input_price
|
26
35
|
end
|
27
36
|
|
37
|
+
# Returns the price per million tokens for output
|
38
|
+
# @param model_id [String] the model identifier
|
39
|
+
# @return [Float] the price per million tokens in USD
|
28
40
|
def output_price_for(model_id)
|
29
41
|
PRICES.dig(model_family(model_id), :output) || default_output_price
|
30
42
|
end
|
31
43
|
|
44
|
+
# Returns the price per million tokens for input with cache hit
|
45
|
+
# @param model_id [String] the model identifier
|
46
|
+
# @return [Float] the price per million tokens in USD
|
32
47
|
def cache_hit_price_for(model_id)
|
33
48
|
PRICES.dig(model_family(model_id), :input_hit) || default_cache_hit_price
|
34
49
|
end
|
35
50
|
|
51
|
+
# Determines if the model supports vision capabilities
|
52
|
+
# @param model_id [String] the model identifier
|
53
|
+
# @return [Boolean] true if the model supports vision
|
36
54
|
def supports_vision?(_model_id)
|
37
55
|
false # DeepSeek models don't currently support vision
|
38
56
|
end
|
39
57
|
|
58
|
+
# Determines if the model supports function calling
|
59
|
+
# @param model_id [String] the model identifier
|
60
|
+
# @return [Boolean] true if the model supports function calling
|
40
61
|
def supports_functions?(model_id)
|
41
62
|
model_id.match?(/deepseek-chat/) # Only deepseek-chat supports function calling
|
42
63
|
end
|
43
64
|
|
65
|
+
# Determines if the model supports JSON mode
|
66
|
+
# @param model_id [String] the model identifier
|
67
|
+
# @return [Boolean] true if the model supports JSON mode
|
44
68
|
def supports_json_mode?(model_id)
|
45
69
|
model_id.match?(/deepseek-chat/) # Only deepseek-chat supports JSON mode
|
46
70
|
end
|
47
71
|
|
72
|
+
# Returns a formatted display name for the model
|
73
|
+
# @param model_id [String] the model identifier
|
74
|
+
# @return [String] the formatted display name
|
48
75
|
def format_display_name(model_id)
|
49
76
|
case model_id
|
50
77
|
when 'deepseek-chat' then 'DeepSeek V3'
|
@@ -56,10 +83,16 @@ module RubyLLM
|
|
56
83
|
end
|
57
84
|
end
|
58
85
|
|
86
|
+
# Returns the model type
|
87
|
+
# @param model_id [String] the model identifier
|
88
|
+
# @return [String] the model type (e.g., 'chat')
|
59
89
|
def model_type(_model_id)
|
60
90
|
'chat' # All DeepSeek models are chat models
|
61
91
|
end
|
62
92
|
|
93
|
+
# Returns the model family
|
94
|
+
# @param model_id [String] the model identifier
|
95
|
+
# @return [Symbol] the model family
|
63
96
|
def model_family(model_id)
|
64
97
|
case model_id
|
65
98
|
when /deepseek-chat/ then :chat
|
@@ -84,14 +117,20 @@ module RubyLLM
|
|
84
117
|
|
85
118
|
private
|
86
119
|
|
120
|
+
# Default input price when model family can't be determined
|
121
|
+
# @return [Float] the default input price
|
87
122
|
def default_input_price
|
88
123
|
0.27 # Default to chat cache miss price
|
89
124
|
end
|
90
125
|
|
126
|
+
# Default output price when model family can't be determined
|
127
|
+
# @return [Float] the default output price
|
91
128
|
def default_output_price
|
92
129
|
1.10 # Default to chat output price
|
93
130
|
end
|
94
131
|
|
132
|
+
# Default cache hit price when model family can't be determined
|
133
|
+
# @return [Float] the default cache hit price
|
95
134
|
def default_cache_hit_price
|
96
135
|
0.07 # Default to chat cache hit price
|
97
136
|
end
|
@@ -7,25 +7,34 @@ module RubyLLM
|
|
7
7
|
module Capabilities # rubocop:disable Metrics/ModuleLength
|
8
8
|
module_function
|
9
9
|
|
10
|
+
# Returns the context window size (input token limit) for the given model
|
11
|
+
# @param model_id [String] the model identifier
|
12
|
+
# @return [Integer] the context window size in tokens
|
10
13
|
def context_window_for(model_id)
|
11
14
|
case model_id
|
12
15
|
when /gemini-2\.0-flash/, /gemini-1\.5-flash/ then 1_048_576
|
13
16
|
when /gemini-1\.5-pro/ then 2_097_152
|
14
|
-
when /text-embedding/, /embedding-001/ then 2_048
|
17
|
+
when /text-embedding-004/, /embedding-001/ then 2_048
|
15
18
|
when /aqa/ then 7_168
|
16
19
|
else 32_768 # Sensible default for unknown models
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
# Returns the maximum output tokens for the given model
|
24
|
+
# @param model_id [String] the model identifier
|
25
|
+
# @return [Integer] the maximum output tokens
|
20
26
|
def max_tokens_for(model_id)
|
21
27
|
case model_id
|
22
28
|
when /gemini-2\.0-flash/, /gemini-1\.5/ then 8_192
|
23
|
-
when /text-embedding/, /embedding-001/ then 768 # Output dimension size for embeddings
|
29
|
+
when /text-embedding-004/, /embedding-001/ then 768 # Output dimension size for embeddings
|
24
30
|
when /aqa/ then 1_024
|
25
31
|
else 4_096 # Sensible default
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
35
|
+
# Returns the input price per million tokens for the given model
|
36
|
+
# @param model_id [String] the model identifier
|
37
|
+
# @return [Float] the price per million tokens in USD
|
29
38
|
def input_price_for(model_id)
|
30
39
|
base_price = PRICES.dig(pricing_family(model_id), :input) || default_input_price
|
31
40
|
return base_price unless long_context_model?(model_id)
|
@@ -34,6 +43,9 @@ module RubyLLM
|
|
34
43
|
context_length(model_id) > 128_000 ? base_price * 2 : base_price
|
35
44
|
end
|
36
45
|
|
46
|
+
# Returns the output price per million tokens for the given model
|
47
|
+
# @param model_id [String] the model identifier
|
48
|
+
# @return [Float] the price per million tokens in USD
|
37
49
|
def output_price_for(model_id)
|
38
50
|
base_price = PRICES.dig(pricing_family(model_id), :output) || default_output_price
|
39
51
|
return base_price unless long_context_model?(model_id)
|
@@ -42,6 +54,9 @@ module RubyLLM
|
|
42
54
|
context_length(model_id) > 128_000 ? base_price * 2 : base_price
|
43
55
|
end
|
44
56
|
|
57
|
+
# Determines if the model supports vision (image/video) inputs
|
58
|
+
# @param model_id [String] the model identifier
|
59
|
+
# @return [Boolean] true if the model supports vision inputs
|
45
60
|
def supports_vision?(model_id)
|
46
61
|
return false if model_id.match?(/text-embedding|embedding-001|aqa/)
|
47
62
|
return false if model_id.match?(/gemini-1\.0/)
|
@@ -49,6 +64,9 @@ module RubyLLM
|
|
49
64
|
model_id.match?(/gemini-[12]\.[05]/)
|
50
65
|
end
|
51
66
|
|
67
|
+
# Determines if the model supports function calling
|
68
|
+
# @param model_id [String] the model identifier
|
69
|
+
# @return [Boolean] true if the model supports function calling
|
52
70
|
def supports_functions?(model_id)
|
53
71
|
return false if model_id.match?(/text-embedding|embedding-001|aqa/)
|
54
72
|
return false if model_id.match?(/flash-lite/)
|
@@ -57,13 +75,20 @@ module RubyLLM
|
|
57
75
|
model_id.match?(/gemini-[12]\.[05]-(?:pro|flash)(?!-lite)/)
|
58
76
|
end
|
59
77
|
|
78
|
+
# Determines if the model supports JSON mode
|
79
|
+
# @param model_id [String] the model identifier
|
80
|
+
# @return [Boolean] true if the model supports JSON mode
|
60
81
|
def supports_json_mode?(model_id)
|
61
82
|
return false if model_id.match?(/text-embedding|embedding-001|aqa/)
|
62
83
|
return false if model_id.match?(/gemini-1\.0/)
|
84
|
+
return false if model_id.match?(/gemini-2\.0-flash-lite/)
|
63
85
|
|
64
86
|
model_id.match?(/gemini-\d/)
|
65
87
|
end
|
66
88
|
|
89
|
+
# Formats the model ID into a human-readable display name
|
90
|
+
# @param model_id [String] the model identifier
|
91
|
+
# @return [String] the formatted display name
|
67
92
|
def format_display_name(model_id)
|
68
93
|
model_id
|
69
94
|
.delete_prefix('models/')
|
@@ -76,20 +101,32 @@ module RubyLLM
|
|
76
101
|
.strip
|
77
102
|
end
|
78
103
|
|
104
|
+
# Determines if the model supports context caching
|
105
|
+
# @param model_id [String] the model identifier
|
106
|
+
# @return [Boolean] true if the model supports caching
|
79
107
|
def supports_caching?(model_id)
|
80
108
|
return false if model_id.match?(/flash-lite|gemini-1\.0/)
|
81
109
|
|
82
110
|
model_id.match?(/gemini-[12]\.[05]/)
|
83
111
|
end
|
84
112
|
|
113
|
+
# Determines if the model supports tuning
|
114
|
+
# @param model_id [String] the model identifier
|
115
|
+
# @return [Boolean] true if the model supports tuning
|
85
116
|
def supports_tuning?(model_id)
|
86
117
|
model_id.match?(/gemini-1\.5-flash/)
|
87
118
|
end
|
88
119
|
|
120
|
+
# Determines if the model supports audio inputs
|
121
|
+
# @param model_id [String] the model identifier
|
122
|
+
# @return [Boolean] true if the model supports audio inputs
|
89
123
|
def supports_audio?(model_id)
|
90
124
|
model_id.match?(/gemini-[12]\.[05]/)
|
91
125
|
end
|
92
126
|
|
127
|
+
# Returns the type of model (chat, embedding, image)
|
128
|
+
# @param model_id [String] the model identifier
|
129
|
+
# @return [String] the model type
|
93
130
|
def model_type(model_id)
|
94
131
|
case model_id
|
95
132
|
when /text-embedding|embedding/ then 'embedding'
|
@@ -98,6 +135,9 @@ module RubyLLM
|
|
98
135
|
end
|
99
136
|
end
|
100
137
|
|
138
|
+
# Returns the model family identifier
|
139
|
+
# @param model_id [String] the model identifier
|
140
|
+
# @return [String] the model family identifier
|
101
141
|
def model_family(model_id) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
102
142
|
case model_id
|
103
143
|
when /gemini-2\.0-flash-lite/ then 'gemini20_flash_lite'
|
@@ -113,7 +153,10 @@ module RubyLLM
|
|
113
153
|
end
|
114
154
|
end
|
115
155
|
|
116
|
-
|
156
|
+
# Returns the pricing family identifier for the model
|
157
|
+
# @param model_id [String] the model identifier
|
158
|
+
# @return [Symbol] the pricing family identifier
|
159
|
+
def pricing_family(model_id) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
117
160
|
case model_id
|
118
161
|
when /gemini-2\.0-flash-lite/ then :flash_lite_2 # rubocop:disable Naming/VariableNumber
|
119
162
|
when /gemini-2\.0-flash/ then :flash_2 # rubocop:disable Naming/VariableNumber
|
@@ -122,20 +165,26 @@ module RubyLLM
|
|
122
165
|
when /gemini-1\.5-pro/ then :pro
|
123
166
|
when /gemini-1\.0-pro/ then :pro_1_0 # rubocop:disable Naming/VariableNumber
|
124
167
|
when /text-embedding|embedding/ then :embedding
|
168
|
+
when /aqa/ then :aqa
|
125
169
|
else :base
|
126
170
|
end
|
127
171
|
end
|
128
172
|
|
129
|
-
|
130
|
-
|
173
|
+
# Determines if the model supports long context
|
174
|
+
# @param model_id [String] the model identifier
|
175
|
+
# @return [Boolean] true if the model supports long context
|
131
176
|
def long_context_model?(model_id)
|
132
177
|
model_id.match?(/gemini-1\.5-(?:pro|flash)/)
|
133
178
|
end
|
134
179
|
|
180
|
+
# Returns the context length for the model
|
181
|
+
# @param model_id [String] the model identifier
|
182
|
+
# @return [Integer] the context length in tokens
|
135
183
|
def context_length(model_id)
|
136
184
|
context_window_for(model_id)
|
137
185
|
end
|
138
186
|
|
187
|
+
# Pricing information for Gemini models (per 1M tokens in USD)
|
139
188
|
PRICES = {
|
140
189
|
flash_2: { # Gemini 2.0 Flash # rubocop:disable Naming/VariableNumber
|
141
190
|
input: 0.10,
|
@@ -154,19 +203,22 @@ module RubyLLM
|
|
154
203
|
input: 0.075,
|
155
204
|
output: 0.30,
|
156
205
|
cache: 0.01875,
|
157
|
-
cache_storage: 1.00
|
206
|
+
cache_storage: 1.00,
|
207
|
+
grounding_search: 35.00 # per 1K requests
|
158
208
|
},
|
159
209
|
flash_8b: { # Gemini 1.5 Flash 8B
|
160
210
|
input: 0.0375,
|
161
211
|
output: 0.15,
|
162
212
|
cache: 0.01,
|
163
|
-
cache_storage: 0.25
|
213
|
+
cache_storage: 0.25,
|
214
|
+
grounding_search: 35.00 # per 1K requests
|
164
215
|
},
|
165
216
|
pro: { # Gemini 1.5 Pro
|
166
217
|
input: 1.25,
|
167
218
|
output: 5.0,
|
168
219
|
cache: 0.3125,
|
169
|
-
cache_storage: 4.50
|
220
|
+
cache_storage: 4.50,
|
221
|
+
grounding_search: 35.00 # per 1K requests
|
170
222
|
},
|
171
223
|
pro_1_0: { # Gemini 1.0 Pro # rubocop:disable Naming/VariableNumber
|
172
224
|
input: 0.50,
|
@@ -175,15 +227,23 @@ module RubyLLM
|
|
175
227
|
embedding: { # Text Embedding models
|
176
228
|
input: 0.00,
|
177
229
|
output: 0.00
|
230
|
+
},
|
231
|
+
aqa: { # AQA model
|
232
|
+
input: 0.00,
|
233
|
+
output: 0.00
|
178
234
|
}
|
179
235
|
}.freeze
|
180
236
|
|
237
|
+
# Default input price for unknown models
|
238
|
+
# @return [Float] the default input price per million tokens
|
181
239
|
def default_input_price
|
182
240
|
0.075 # Default to Flash pricing
|
183
241
|
end
|
184
242
|
|
243
|
+
# Default output price for unknown models
|
244
|
+
# @return [Float] the default output price per million tokens
|
185
245
|
def default_output_price
|
186
|
-
0.30
|
246
|
+
0.30 # Default to Flash pricing
|
187
247
|
end
|
188
248
|
end
|
189
249
|
end
|
@@ -7,12 +7,12 @@ module RubyLLM
|
|
7
7
|
module Models
|
8
8
|
module_function
|
9
9
|
|
10
|
-
def parse_list_models_response(response)
|
10
|
+
def parse_list_models_response(response, slug, capabilities)
|
11
11
|
response.body['data']&.each do |model|
|
12
12
|
model['id'] = model['id'].delete_prefix('models/')
|
13
13
|
end
|
14
14
|
|
15
|
-
OpenAI::Models.parse_list_models_response(response)
|
15
|
+
OpenAI::Models.parse_list_models_response(response, slug, capabilities)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -7,76 +7,113 @@ module RubyLLM
|
|
7
7
|
module Capabilities # rubocop:disable Metrics/ModuleLength
|
8
8
|
module_function
|
9
9
|
|
10
|
+
# Returns the context window size for the given model ID
|
11
|
+
# @param model_id [String] the model identifier
|
12
|
+
# @return [Integer] the context window size in tokens
|
10
13
|
def context_window_for(model_id)
|
11
14
|
case model_id
|
12
|
-
when /
|
13
|
-
when /o1-
|
14
|
-
when /gpt-
|
15
|
-
when /gpt-
|
16
|
-
when /gpt-3.5
|
17
|
-
when /
|
15
|
+
when /o1-2024/, /o3-mini/, /o3-mini-2025/ then 200_000
|
16
|
+
when /gpt-4o/, /gpt-4o-mini/, /gpt-4-turbo/, /o1-mini/ then 128_000
|
17
|
+
when /gpt-4-0[0-9]{3}/ then 8_192
|
18
|
+
when /gpt-3.5-turbo-instruct/ then 4_096
|
19
|
+
when /gpt-3.5/ then 16_385
|
20
|
+
when /babbage-002/, /davinci-002/ then 16_384
|
18
21
|
else 4_096
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
25
|
+
# Returns the maximum output tokens for the given model ID
|
26
|
+
# @param model_id [String] the model identifier
|
27
|
+
# @return [Integer] the maximum output tokens
|
28
|
+
def max_tokens_for(model_id) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
23
29
|
case model_id
|
24
|
-
when /o1-2024/, /o3-mini/
|
25
|
-
when /o1-mini-2024/
|
26
|
-
when /gpt-4o-2024-05-13/
|
27
|
-
when /gpt-4o/, /gpt-4o-mini/
|
28
|
-
when /gpt-4o-
|
29
|
-
when /gpt-4-0[0-9]{3}/
|
30
|
-
when /gpt-3.5-turbo/
|
30
|
+
when /o1-2024/, /o3-mini/, /o3-mini-2025/ then 100_000
|
31
|
+
when /o1-mini-2024/ then 65_536
|
32
|
+
when /gpt-4o-2024-05-13/ then 4_096
|
33
|
+
when /gpt-4o-realtime/, /gpt-4o-mini-realtime/ then 4_096
|
34
|
+
when /gpt-4o/, /gpt-4o-mini/, /gpt-4o-audio/, /gpt-4o-mini-audio/ then 16_384
|
35
|
+
when /gpt-4-0[0-9]{3}/ then 8_192
|
36
|
+
when /gpt-4-turbo/, /gpt-3.5-turbo/ then 4_096
|
37
|
+
when /babbage-002/, /davinci-002/ then 16_384
|
31
38
|
else 4_096
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
42
|
+
# Returns the input price per million tokens for the given model ID
|
43
|
+
# @param model_id [String] the model identifier
|
44
|
+
# @return [Float] the price per million tokens for input
|
35
45
|
def input_price_for(model_id)
|
36
46
|
PRICES.dig(model_family(model_id), :input) || default_input_price
|
37
47
|
end
|
38
48
|
|
49
|
+
# Returns the output price per million tokens for the given model ID
|
50
|
+
# @param model_id [String] the model identifier
|
51
|
+
# @return [Float] the price per million tokens for output
|
39
52
|
def output_price_for(model_id)
|
40
53
|
PRICES.dig(model_family(model_id), :output) || default_output_price
|
41
54
|
end
|
42
55
|
|
56
|
+
# Determines if the model supports vision capabilities
|
57
|
+
# @param model_id [String] the model identifier
|
58
|
+
# @return [Boolean] true if the model supports vision
|
43
59
|
def supports_vision?(model_id)
|
44
60
|
model_id.match?(/gpt-4o|o1/) || model_id.match?(/gpt-4-(?!0314|0613)/)
|
45
61
|
end
|
46
62
|
|
63
|
+
# Determines if the model supports function calling
|
64
|
+
# @param model_id [String] the model identifier
|
65
|
+
# @return [Boolean] true if the model supports functions
|
47
66
|
def supports_functions?(model_id)
|
48
67
|
!model_id.include?('instruct')
|
49
68
|
end
|
50
69
|
|
70
|
+
# Determines if the model supports audio input/output
|
71
|
+
# @param model_id [String] the model identifier
|
72
|
+
# @return [Boolean] true if the model supports audio
|
51
73
|
def supports_audio?(model_id)
|
52
74
|
model_id.match?(/audio-preview|realtime-preview|whisper|tts/)
|
53
75
|
end
|
54
76
|
|
77
|
+
# Determines if the model supports JSON mode
|
78
|
+
# @param model_id [String] the model identifier
|
79
|
+
# @return [Boolean] true if the model supports JSON mode
|
55
80
|
def supports_json_mode?(model_id)
|
56
81
|
model_id.match?(/gpt-4-\d{4}-preview/) ||
|
57
82
|
model_id.include?('turbo') ||
|
58
83
|
model_id.match?(/gpt-3.5-turbo-(?!0301|0613)/)
|
59
84
|
end
|
60
85
|
|
86
|
+
# Formats the model ID into a human-readable display name
|
87
|
+
# @param model_id [String] the model identifier
|
88
|
+
# @return [String] the formatted display name
|
61
89
|
def format_display_name(model_id)
|
62
90
|
model_id.then { |id| humanize(id) }
|
63
91
|
.then { |name| apply_special_formatting(name) }
|
64
92
|
end
|
65
93
|
|
94
|
+
# Determines the type of model
|
95
|
+
# @param model_id [String] the model identifier
|
96
|
+
# @return [String] the model type (chat, embedding, image, audio, moderation)
|
66
97
|
def model_type(model_id)
|
67
98
|
case model_id
|
68
99
|
when /text-embedding|embedding/ then 'embedding'
|
69
100
|
when /dall-e/ then 'image'
|
70
101
|
when /tts|whisper/ then 'audio'
|
71
|
-
when /omni-moderation/ then 'moderation'
|
102
|
+
when /omni-moderation|text-moderation/ then 'moderation'
|
72
103
|
else 'chat'
|
73
104
|
end
|
74
105
|
end
|
75
106
|
|
107
|
+
# Determines if the model supports structured output
|
108
|
+
# @param model_id [String] the model identifier
|
109
|
+
# @return [Boolean] true if the model supports structured output
|
76
110
|
def supports_structured_output?(model_id)
|
77
|
-
model_id.match?(/gpt-4o|o[13]-mini|o1/)
|
111
|
+
model_id.match?(/gpt-4o|o[13]-mini|o1|o3-mini/)
|
78
112
|
end
|
79
113
|
|
114
|
+
# Determines the model family for pricing and capability lookup
|
115
|
+
# @param model_id [String] the model identifier
|
116
|
+
# @return [Symbol] the model family identifier
|
80
117
|
def model_family(model_id) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
|
81
118
|
case model_id
|
82
119
|
when /o3-mini/ then 'o3_mini'
|
@@ -100,15 +137,14 @@ module RubyLLM
|
|
100
137
|
when /tts-1-hd/ then 'tts1_hd'
|
101
138
|
when /tts-1/ then 'tts1'
|
102
139
|
when /whisper/ then 'whisper1'
|
103
|
-
when /omni-moderation/ then 'moderation'
|
140
|
+
when /omni-moderation|text-moderation/ then 'moderation'
|
104
141
|
when /babbage/ then 'babbage'
|
105
142
|
when /davinci/ then 'davinci'
|
106
143
|
else 'other'
|
107
144
|
end
|
108
145
|
end
|
109
146
|
|
110
|
-
|
111
|
-
|
147
|
+
# Pricing information for OpenAI models (per million tokens unless otherwise specified)
|
112
148
|
PRICES = {
|
113
149
|
o1: { input: 15.0, cached_input: 7.5, output: 60.0 },
|
114
150
|
o1_mini: { input: 1.10, cached_input: 0.55, output: 4.40 },
|
@@ -152,19 +188,27 @@ module RubyLLM
|
|
152
188
|
embedding2: { price: 0.10 },
|
153
189
|
davinci: { input: 2.0, output: 2.0 },
|
154
190
|
babbage: { input: 0.40, output: 0.40 },
|
155
|
-
tts1: { price: 15.0 },
|
156
|
-
tts1_hd: { price: 30.0 },
|
157
|
-
whisper1: { price: 0.006 }
|
191
|
+
tts1: { price: 15.0 }, # per million characters
|
192
|
+
tts1_hd: { price: 30.0 }, # per million characters
|
193
|
+
whisper1: { price: 0.006 }, # per minute
|
194
|
+
moderation: { price: 0.0 } # free
|
158
195
|
}.freeze
|
159
196
|
|
197
|
+
# Default input price when model-specific pricing is not available
|
198
|
+
# @return [Float] the default price per million tokens
|
160
199
|
def default_input_price
|
161
200
|
0.50
|
162
201
|
end
|
163
202
|
|
203
|
+
# Default output price when model-specific pricing is not available
|
204
|
+
# @return [Float] the default price per million tokens
|
164
205
|
def default_output_price
|
165
206
|
1.50
|
166
207
|
end
|
167
208
|
|
209
|
+
# Converts a model ID to a human-readable format
|
210
|
+
# @param id [String] the model identifier
|
211
|
+
# @return [String] the humanized model name
|
168
212
|
def humanize(id)
|
169
213
|
id.tr('-', ' ')
|
170
214
|
.split(' ')
|
@@ -172,18 +216,25 @@ module RubyLLM
|
|
172
216
|
.join(' ')
|
173
217
|
end
|
174
218
|
|
219
|
+
# Applies special formatting rules to model names
|
220
|
+
# @param name [String] the humanized model name
|
221
|
+
# @return [String] the specially formatted model name
|
175
222
|
def apply_special_formatting(name) # rubocop:disable Metrics/MethodLength
|
176
223
|
name
|
177
224
|
.gsub(/(\d{4}) (\d{2}) (\d{2})/, '\1\2\3')
|
178
225
|
.gsub(/^Gpt /, 'GPT-')
|
179
226
|
.gsub(/^O([13]) /, 'O\1-')
|
227
|
+
.gsub(/^O3 Mini/, 'O3-Mini')
|
228
|
+
.gsub(/^O1 Mini/, 'O1-Mini')
|
180
229
|
.gsub(/^Chatgpt /, 'ChatGPT-')
|
181
230
|
.gsub(/^Tts /, 'TTS-')
|
182
231
|
.gsub(/^Dall E /, 'DALL-E-')
|
183
232
|
.gsub(/3\.5 /, '3.5-')
|
184
233
|
.gsub(/4 /, '4-')
|
185
|
-
.gsub(/4o (?=Mini|Preview|Turbo|Audio)/, '4o-')
|
234
|
+
.gsub(/4o (?=Mini|Preview|Turbo|Audio|Realtime)/, '4o-')
|
186
235
|
.gsub(/\bHd\b/, 'HD')
|
236
|
+
.gsub(/Omni Moderation/, 'Omni-Moderation')
|
237
|
+
.gsub(/Text Moderation/, 'Text-Moderation')
|
187
238
|
end
|
188
239
|
end
|
189
240
|
end
|
@@ -11,7 +11,7 @@ module RubyLLM
|
|
11
11
|
'models'
|
12
12
|
end
|
13
13
|
|
14
|
-
def parse_list_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
14
|
+
def parse_list_models_response(response, slug, capabilities) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
15
15
|
(response.body['data'] || []).map do |model|
|
16
16
|
ModelInfo.new(
|
17
17
|
id: model['id'],
|
data/lib/ruby_llm/version.rb
CHANGED
data/lib/tasks/models.rake
CHANGED
@@ -12,10 +12,13 @@ PROVIDER_DOCS = {
|
|
12
12
|
},
|
13
13
|
gemini: {
|
14
14
|
models: 'https://ai.google.dev/gemini-api/docs/models/gemini',
|
15
|
-
pricing: 'https://ai.google.dev/pricing'
|
15
|
+
pricing: 'https://ai.google.dev/gemini-api/docs/pricing'
|
16
16
|
},
|
17
17
|
deepseek: {
|
18
18
|
models: 'https://api-docs.deepseek.com/quick_start/pricing/'
|
19
|
+
},
|
20
|
+
anthropic: {
|
21
|
+
models: 'https://docs.anthropic.com/en/docs/about-claude/models/all-models'
|
19
22
|
}
|
20
23
|
}.freeze
|
21
24
|
|
@@ -85,8 +88,10 @@ namespace :models do # rubocop:disable Metrics/BlockLength
|
|
85
88
|
end
|
86
89
|
end
|
87
90
|
|
88
|
-
desc 'Update model capabilities modules by scraping provider documentation'
|
91
|
+
desc 'Update model capabilities modules by scraping provider documentation (use PROVIDER=name to update only one)'
|
89
92
|
task :update_capabilities do # rubocop:disable Metrics/BlockLength
|
93
|
+
# Check if a specific provider was requested
|
94
|
+
target_provider = ENV['PROVIDER']&.to_sym
|
90
95
|
require 'ruby_llm'
|
91
96
|
require 'fileutils'
|
92
97
|
|
@@ -97,16 +102,24 @@ namespace :models do # rubocop:disable Metrics/BlockLength
|
|
97
102
|
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY')
|
98
103
|
end
|
99
104
|
|
105
|
+
# Filter providers if a specific one was requested
|
106
|
+
providers_to_process = if target_provider && PROVIDER_DOCS.key?(target_provider)
|
107
|
+
{ target_provider => PROVIDER_DOCS[target_provider] }
|
108
|
+
else
|
109
|
+
PROVIDER_DOCS
|
110
|
+
end
|
111
|
+
|
100
112
|
# Process each provider
|
101
|
-
|
113
|
+
providers_to_process.each do |provider, urls| # rubocop:disable Metrics/BlockLength
|
102
114
|
puts "Processing #{provider}..."
|
103
115
|
|
104
116
|
# Initialize our AI assistants
|
117
|
+
#
|
105
118
|
gemini = RubyLLM.chat(model: 'gemini-2.0-flash').with_temperature(0)
|
106
|
-
claude = RubyLLM.chat(model: 'claude-3-
|
119
|
+
claude = RubyLLM.chat(model: 'claude-3-7-sonnet-20250219').with_temperature(0)
|
107
120
|
|
108
121
|
# Read existing capabilities file if present
|
109
|
-
existing_file = "lib/ruby_llm/
|
122
|
+
existing_file = "lib/ruby_llm/providers/#{provider}/capabilities.rb"
|
110
123
|
existing_code = File.read(existing_file) if File.exist?(existing_file)
|
111
124
|
|
112
125
|
begin
|
@@ -155,18 +168,17 @@ namespace :models do # rubocop:disable Metrics/BlockLength
|
|
155
168
|
|
156
169
|
#{model_info}
|
157
170
|
|
158
|
-
The module should go in lib/ruby_llm/
|
171
|
+
The module should go in lib/ruby_llm/providers/#{provider}/capabilities.rb and follow these conventions:
|
159
172
|
|
160
|
-
1.
|
161
|
-
2.
|
162
|
-
3.
|
163
|
-
4.
|
164
|
-
5.
|
165
|
-
6.
|
166
|
-
7. Include
|
167
|
-
8. Include all necessary method documentation
|
173
|
+
1. Include methods for determining context windows, token limits, pricing, and capabilities
|
174
|
+
2. Use consistent naming with other providers
|
175
|
+
3. Include detailed pricing information in a PRICES constant
|
176
|
+
4. Follow the existing structure in the codebase
|
177
|
+
5. Use Ruby idioms and clean code practices
|
178
|
+
6. Include module_function to make methods callable at module level
|
179
|
+
7. Include all necessary method documentation
|
168
180
|
|
169
|
-
Here's the existing implementation for reference (maintain similar structure):
|
181
|
+
Here's the existing implementation for reference (maintain similar structure and same method names):
|
170
182
|
|
171
183
|
#{existing_code}
|
172
184
|
|
@@ -175,12 +187,22 @@ namespace :models do # rubocop:disable Metrics/BlockLength
|
|
175
187
|
|
176
188
|
response = claude.ask(code_prompt)
|
177
189
|
|
190
|
+
# Extract Ruby code from Claude's response
|
191
|
+
puts " Extracting Ruby code from Claude's response..."
|
192
|
+
ruby_code = nil
|
193
|
+
|
194
|
+
# Look for Ruby code block
|
195
|
+
ruby_code = Regexp.last_match(1).strip if response.content =~ /```ruby\s*(.*?)```/m
|
196
|
+
|
197
|
+
# Verify we found Ruby code
|
198
|
+
raise "No Ruby code block found in Claude's response" if ruby_code.nil? || ruby_code.empty?
|
199
|
+
|
178
200
|
# Save the file
|
179
|
-
file_path = "lib/ruby_llm/
|
201
|
+
file_path = "lib/ruby_llm/providers/#{provider}/capabilities.rb"
|
180
202
|
puts " Writing #{file_path}..."
|
181
203
|
|
182
204
|
FileUtils.mkdir_p(File.dirname(file_path))
|
183
|
-
File.write(file_path,
|
205
|
+
File.write(file_path, ruby_code)
|
184
206
|
rescue StandardError => e
|
185
207
|
raise "Failed to process #{provider}: #{e.message}"
|
186
208
|
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.pre36
|
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-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: event_stream_parser
|