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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77ec20e57439d352e965de0e435d0c43b3acd53de2d6035345f6ac6e716e7fef
4
- data.tar.gz: 51d838bc1411303fd96c2f28d160ad99657f44846bd12ad2967ee329880e8922
3
+ metadata.gz: ed17bc0b342342484bd1e92b57f3b88a74d77cd43daeb1a569b132188025bafd
4
+ data.tar.gz: '0970e337a393e85cff88a449e723aa7c3e3e189b1923e16cf619f95d1bf65b03'
5
5
  SHA512:
6
- metadata.gz: 9b0ca7a80113ed498125c9e23460879328926e6f3de1e6fe7e7a63453deb94fbdebbe7961d529e6cd50db7ad91776cb92ffeaed29f396aa523f931b12f835f7d
7
- data.tar.gz: bcb30504074335dbdea39d171a3d810cfb14c7f5f9a3cc5b10e1a80970ce06f37775c7b1c5352a2e5fef0a14ea0d6603ae63197be6d2a866c7003b091036c842
6
+ metadata.gz: 1e8b35980c57cd61e10c50b3eb97dbedbe138e2720b11940646c74807ada4c8b260aff3cbee8ed84abfa4b998d3d2747ff5f2421731fcf73ea038306774a2dc9
7
+ data.tar.gz: f0211b49713b10e00f1070fa7abb00a151ee7084e3f5e14b047be2c567afe4fcd5ade1ce662697a776470ab4e6c21e98ab1c453526e36b4ec29d6f7f6b9f6c0c
@@ -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.075,
15
- "output_price_per_million": 0.3,
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": 4096,
27
- "max_tokens": 4096,
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": 100000,
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": 100000,
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": false,
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": 4096,
250
- "max_tokens": 4096,
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": true,
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": true,
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": 16384,
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": 16384,
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 Realtime Preview",
1688
+ "display_name": "GPT-4o-Realtime Preview",
1578
1689
  "provider": "openai",
1579
1690
  "context_window": 128000,
1580
- "max_tokens": 16384,
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 Realtime Preview 20241001",
1707
+ "display_name": "GPT-4o-Realtime Preview 20241001",
1597
1708
  "provider": "openai",
1598
1709
  "context_window": 128000,
1599
- "max_tokens": 16384,
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 Realtime Preview 20241217",
1726
+ "display_name": "GPT-4o-Realtime Preview 20241217",
1616
1727
  "provider": "openai",
1617
1728
  "context_window": 128000,
1618
- "max_tokens": 16384,
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": 200000,
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": 200000,
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 Moderation 20240926",
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 Moderation Latest",
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",
@@ -53,9 +53,7 @@ module RubyLLM
53
53
  end
54
54
 
55
55
  def refresh!
56
- @all = RubyLLM.providers.flat_map do |provider|
57
- provider.new.list_models
58
- end.sort_by(&:id)
56
+ @all = RubyLLM.providers.flat_map(&:list_models).sort_by(&:id)
59
57
  end
60
58
  end
61
59
  end
@@ -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
- def determine_context_window(model_id)
11
- case model_id
12
- when /claude-3/ then 200_000
13
- else 100_000
14
- end
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
- return false if model_id.match?(/claude-3-5-haiku/)
34
- return false if model_id.match?(/claude-[12]/)
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.include?('claude-3')
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.include?('claude-3')
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
- claude35_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
64
- claude35_haiku: { input: 0.80, output: 4.0 }, # $0.80/$4.00 per million tokens
65
- claude3_opus: { input: 15.0, output: 75.0 }, # $15.00/$75.00 per million tokens
66
- claude3_sonnet: { input: 3.0, output: 15.0 }, # $3.00/$15.00 per million tokens
67
- claude3_haiku: { input: 0.25, output: 1.25 }, # $0.25/$1.25 per million tokens
68
- claude2: { input: 3.0, output: 15.0 } # Default pricing for Claude 2.x models
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'],
@@ -24,6 +24,14 @@ module RubyLLM
24
24
  'anthropic-version' => '2023-06-01'
25
25
  }
26
26
  end
27
+
28
+ def capabilities
29
+ Anthropic::Capabilities
30
+ end
31
+
32
+ def slug
33
+ 'anthropic'
34
+ end
27
35
  end
28
36
  end
29
37
  end
@@ -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
@@ -17,6 +17,14 @@ module RubyLLM
17
17
  'Authorization' => "Bearer #{RubyLLM.config.deepseek_api_key}"
18
18
  }
19
19
  end
20
+
21
+ def capabilities
22
+ DeepSeek::Capabilities
23
+ end
24
+
25
+ def slug
26
+ 'deepseek'
27
+ end
20
28
  end
21
29
  end
22
30
  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
- def pricing_family(model_id) # rubocop:disable Metrics/CyclomaticComplexity
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
- private
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 # Default to Flash pricing
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
@@ -18,6 +18,14 @@ module RubyLLM
18
18
  'Authorization' => "Bearer #{RubyLLM.config.gemini_api_key}"
19
19
  }
20
20
  end
21
+
22
+ def capabilities
23
+ Gemini::Capabilities
24
+ end
25
+
26
+ def slug
27
+ 'gemini'
28
+ end
21
29
  end
22
30
  end
23
31
  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 /o[13]-mini/, /o3-mini-2025/ then 200_000
13
- when /o1-2024/ then 200_000
14
- when /gpt-4o/, /gpt-4-turbo/ then 128_000
15
- when /gpt-4-0[0-9]{3}/ then 8_192
16
- when /gpt-3.5-turbo-instruct/ then 4_096
17
- when /gpt-3.5/ then 16_385
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
- def max_tokens_for(model_id) # rubocop:disable Metrics/CyclomaticComplexity
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/ then 100_000
25
- when /o1-mini-2024/ then 65_536
26
- when /gpt-4o-2024-05-13/ then 4_096
27
- when /gpt-4o/, /gpt-4o-mini/ then 16_384
28
- when /gpt-4o-realtime/ then 4_096
29
- when /gpt-4-0[0-9]{3}/ then 8_192
30
- when /gpt-3.5-turbo/ then 4_096
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
- private
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'],
@@ -37,6 +37,14 @@ module RubyLLM
37
37
  'Authorization' => "Bearer #{RubyLLM.config.openai_api_key}"
38
38
  }
39
39
  end
40
+
41
+ def capabilities
42
+ OpenAI::Capabilities
43
+ end
44
+
45
+ def slug
46
+ 'openai'
47
+ end
40
48
  end
41
49
  end
42
50
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '0.1.0.pre34'
4
+ VERSION = '0.1.0.pre36'
5
5
  end
@@ -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
- PROVIDER_DOCS.each do |provider, urls| # rubocop:disable Metrics/BlockLength
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-5-sonnet-20241022').with_temperature(0)
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/model_capabilities/#{provider}.rb"
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/model_capabilities/#{provider}.rb and follow these conventions:
171
+ The module should go in lib/ruby_llm/providers/#{provider}/capabilities.rb and follow these conventions:
159
172
 
160
- 1. Module name should be RubyLLM::ModelCapabilities::#{provider.to_s.capitalize}
161
- 2. Include methods for determining context windows, token limits, pricing, and capabilities
162
- 3. Use consistent naming with other providers
163
- 4. Include detailed pricing information in a PRICES constant
164
- 5. Follow the existing structure in the codebase
165
- 6. Use Ruby idioms and clean code practices
166
- 7. Include module_function to make methods callable at module level
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/model_capabilities/#{provider}.rb"
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, response.content)
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.pre34
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-21 00:00:00.000000000 Z
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