ruby_llm 1.2.0 → 1.3.0rc1

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -133
  3. data/lib/ruby_llm/active_record/acts_as.rb +212 -33
  4. data/lib/ruby_llm/aliases.json +48 -6
  5. data/lib/ruby_llm/attachments/audio.rb +12 -0
  6. data/lib/ruby_llm/attachments/image.rb +9 -0
  7. data/lib/ruby_llm/attachments/pdf.rb +9 -0
  8. data/lib/ruby_llm/attachments.rb +78 -0
  9. data/lib/ruby_llm/chat.rb +22 -19
  10. data/lib/ruby_llm/configuration.rb +30 -1
  11. data/lib/ruby_llm/connection.rb +95 -0
  12. data/lib/ruby_llm/content.rb +51 -72
  13. data/lib/ruby_llm/context.rb +30 -0
  14. data/lib/ruby_llm/embedding.rb +13 -5
  15. data/lib/ruby_llm/error.rb +1 -1
  16. data/lib/ruby_llm/image.rb +13 -5
  17. data/lib/ruby_llm/message.rb +12 -4
  18. data/lib/ruby_llm/mime_types.rb +713 -0
  19. data/lib/ruby_llm/model_info.rb +208 -27
  20. data/lib/ruby_llm/models.json +25766 -2154
  21. data/lib/ruby_llm/models.rb +95 -14
  22. data/lib/ruby_llm/provider.rb +48 -90
  23. data/lib/ruby_llm/providers/anthropic/capabilities.rb +76 -13
  24. data/lib/ruby_llm/providers/anthropic/chat.rb +7 -14
  25. data/lib/ruby_llm/providers/anthropic/media.rb +44 -34
  26. data/lib/ruby_llm/providers/anthropic/models.rb +15 -15
  27. data/lib/ruby_llm/providers/anthropic/tools.rb +2 -2
  28. data/lib/ruby_llm/providers/anthropic.rb +3 -3
  29. data/lib/ruby_llm/providers/bedrock/capabilities.rb +61 -2
  30. data/lib/ruby_llm/providers/bedrock/chat.rb +30 -73
  31. data/lib/ruby_llm/providers/bedrock/media.rb +56 -0
  32. data/lib/ruby_llm/providers/bedrock/models.rb +50 -58
  33. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +16 -0
  34. data/lib/ruby_llm/providers/bedrock.rb +14 -25
  35. data/lib/ruby_llm/providers/deepseek/capabilities.rb +35 -2
  36. data/lib/ruby_llm/providers/deepseek.rb +3 -3
  37. data/lib/ruby_llm/providers/gemini/capabilities.rb +84 -3
  38. data/lib/ruby_llm/providers/gemini/chat.rb +8 -37
  39. data/lib/ruby_llm/providers/gemini/embeddings.rb +18 -34
  40. data/lib/ruby_llm/providers/gemini/images.rb +2 -2
  41. data/lib/ruby_llm/providers/gemini/media.rb +39 -110
  42. data/lib/ruby_llm/providers/gemini/models.rb +16 -22
  43. data/lib/ruby_llm/providers/gemini/tools.rb +1 -1
  44. data/lib/ruby_llm/providers/gemini.rb +3 -3
  45. data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
  46. data/lib/ruby_llm/providers/ollama/media.rb +44 -0
  47. data/lib/ruby_llm/providers/ollama.rb +34 -0
  48. data/lib/ruby_llm/providers/openai/capabilities.rb +78 -3
  49. data/lib/ruby_llm/providers/openai/chat.rb +6 -4
  50. data/lib/ruby_llm/providers/openai/embeddings.rb +8 -12
  51. data/lib/ruby_llm/providers/openai/media.rb +38 -21
  52. data/lib/ruby_llm/providers/openai/models.rb +16 -17
  53. data/lib/ruby_llm/providers/openai/tools.rb +9 -5
  54. data/lib/ruby_llm/providers/openai.rb +7 -5
  55. data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
  56. data/lib/ruby_llm/providers/openrouter.rb +31 -0
  57. data/lib/ruby_llm/stream_accumulator.rb +4 -4
  58. data/lib/ruby_llm/streaming.rb +3 -3
  59. data/lib/ruby_llm/utils.rb +22 -0
  60. data/lib/ruby_llm/version.rb +1 -1
  61. data/lib/ruby_llm.rb +15 -5
  62. data/lib/tasks/models.rake +69 -33
  63. data/lib/tasks/models_docs.rake +164 -121
  64. data/lib/tasks/vcr.rake +4 -2
  65. metadata +23 -14
  66. data/lib/tasks/browser_helper.rb +0 -97
  67. data/lib/tasks/capability_generator.rb +0 -123
  68. data/lib/tasks/capability_scraper.rb +0 -224
  69. data/lib/tasks/cli_helper.rb +0 -22
  70. data/lib/tasks/code_validator.rb +0 -29
  71. data/lib/tasks/model_updater.rb +0 -66
@@ -15,13 +15,13 @@ module RubyLLM
15
15
 
16
16
  module_function
17
17
 
18
- def api_base
18
+ def api_base(_config)
19
19
  'https://api.anthropic.com'
20
20
  end
21
21
 
22
- def headers
22
+ def headers(config)
23
23
  {
24
- 'x-api-key' => RubyLLM.config.anthropic_api_key,
24
+ 'x-api-key' => config.anthropic_api_key,
25
25
  'anthropic-version' => '2023-06-01'
26
26
  }
27
27
  end
@@ -149,8 +149,6 @@ module RubyLLM
149
149
  0.2
150
150
  end
151
151
 
152
- private
153
-
154
152
  # Converts a model ID to a human-readable format
155
153
  # @param id [String] the model identifier
156
154
  # @return [String] the humanized model name
@@ -162,6 +160,67 @@ module RubyLLM
162
160
  .map(&:capitalize)
163
161
  .join(' ')
164
162
  end
163
+
164
+ def modalities_for(model_id)
165
+ modalities = {
166
+ input: ['text'],
167
+ output: ['text']
168
+ }
169
+
170
+ # Vision support for Claude models
171
+ if model_id.match?(/anthropic\.claude/) && supports_vision?(model_id)
172
+ modalities[:input] << 'image'
173
+ modalities[:input] << 'pdf'
174
+ end
175
+
176
+ modalities
177
+ end
178
+
179
+ def capabilities_for(model_id)
180
+ capabilities = []
181
+
182
+ # Streaming
183
+ capabilities << 'streaming' if model_id.match?(/anthropic\.claude/)
184
+
185
+ # Function calling & structured output
186
+ capabilities << 'function_calling' if supports_functions?(model_id)
187
+
188
+ capabilities << 'structured_output' if supports_json_mode?(model_id)
189
+
190
+ # Extended thinking for 3.7 models
191
+ capabilities << 'reasoning' if model_id.match?(/claude-3-7/)
192
+
193
+ # Batch capabilities for newer Claude models
194
+ if model_id.match?(/claude-3\.5|claude-3-7/)
195
+ capabilities << 'batch'
196
+ capabilities << 'citations'
197
+ end
198
+
199
+ capabilities
200
+ end
201
+
202
+ def pricing_for(model_id)
203
+ family = model_family(model_id)
204
+ prices = PRICES.fetch(family, { input: default_input_price, output: default_output_price })
205
+
206
+ standard_pricing = {
207
+ input_per_million: prices[:input],
208
+ output_per_million: prices[:output]
209
+ }
210
+
211
+ # Batch pricing - typically 50% of standard
212
+ batch_pricing = {
213
+ input_per_million: prices[:input] * 0.5,
214
+ output_per_million: prices[:output] * 0.5
215
+ }
216
+
217
+ {
218
+ text_tokens: {
219
+ standard: standard_pricing,
220
+ batch: batch_pricing
221
+ }
222
+ }
223
+ end
165
224
  end
166
225
  end
167
226
  end
@@ -5,58 +5,22 @@ module RubyLLM
5
5
  module Bedrock
6
6
  # Chat methods for the AWS Bedrock API implementation
7
7
  module Chat
8
- private
9
-
10
- def completion_url
11
- "model/#{@model_id}/invoke"
12
- end
13
-
14
- def render_payload(messages, tools:, temperature:, model:, stream: false) # rubocop:disable Lint/UnusedMethodArgument
15
- # Hold model_id in instance variable for use in completion_url and stream_url
16
- @model_id = model
8
+ module_function
17
9
 
18
- system_messages, chat_messages = separate_messages(messages)
19
- system_content = build_system_content(system_messages)
20
-
21
- build_base_payload(chat_messages, temperature, model).tap do |payload|
22
- add_optional_fields(payload, system_content:, tools:)
10
+ def sync_response(connection, payload)
11
+ signature = sign_request("#{connection.connection.url_prefix}#{completion_url}", config: connection.config,
12
+ payload:)
13
+ response = connection.post completion_url, payload do |req|
14
+ req.headers.merge! build_headers(signature.headers, streaming: block_given?)
23
15
  end
24
- end
25
-
26
- def separate_messages(messages)
27
- messages.partition { |msg| msg.role == :system }
28
- end
29
-
30
- def build_system_content(system_messages)
31
- if system_messages.length > 1
32
- RubyLLM.logger.warn(
33
- "Amazon Bedrock's Claude implementation only supports a single system message. " \
34
- 'Multiple system messages will be combined into one.'
35
- )
36
- end
37
-
38
- system_messages.map { |msg| format_message(msg)[:content] }.join("\n\n")
39
- end
40
-
41
- def build_base_payload(chat_messages, temperature, model)
42
- {
43
- anthropic_version: 'bedrock-2023-05-31',
44
- messages: chat_messages.map { |msg| format_message(msg) },
45
- temperature: temperature,
46
- max_tokens: RubyLLM.models.find(model).max_tokens
47
- }
48
- end
49
-
50
- def add_optional_fields(payload, system_content:, tools:)
51
- payload[:tools] = tools.values.map { |t| function_for(t) } if tools.any?
52
- payload[:system] = system_content unless system_content.empty?
16
+ Anthropic::Chat.parse_completion_response response
53
17
  end
54
18
 
55
19
  def format_message(msg)
56
20
  if msg.tool_call?
57
- format_tool_call(msg)
21
+ Anthropic::Tools.format_tool_call(msg)
58
22
  elsif msg.tool_result?
59
- format_tool_result(msg)
23
+ Anthropic::Tools.format_tool_result(msg)
60
24
  else
61
25
  format_basic_message(msg)
62
26
  end
@@ -64,43 +28,36 @@ module RubyLLM
64
28
 
65
29
  def format_basic_message(msg)
66
30
  {
67
- role: convert_role(msg.role),
68
- content: Anthropic::Media.format_content(msg.content)
31
+ role: Anthropic::Chat.convert_role(msg.role),
32
+ content: Media.format_content(msg.content)
69
33
  }
70
34
  end
71
35
 
72
- def convert_role(role)
73
- case role
74
- when :tool, :user then 'user'
75
- when :system then 'system'
76
- else 'assistant'
77
- end
78
- end
36
+ private
79
37
 
80
- def parse_completion_response(response)
81
- data = response.body
82
- content_blocks = data['content'] || []
38
+ def completion_url
39
+ "model/#{@model_id}/invoke"
40
+ end
83
41
 
84
- text_content = extract_text_content(content_blocks)
85
- tool_use = find_tool_use(content_blocks)
42
+ def render_payload(messages, tools:, temperature:, model:, stream: false) # rubocop:disable Lint/UnusedMethodArgument
43
+ # Hold model_id in instance variable for use in completion_url and stream_url
44
+ @model_id = model
86
45
 
87
- build_message(data, text_content, tool_use)
88
- end
46
+ system_messages, chat_messages = Anthropic::Chat.separate_messages(messages)
47
+ system_content = Anthropic::Chat.build_system_content(system_messages)
89
48
 
90
- def extract_text_content(blocks)
91
- text_blocks = blocks.select { |c| c['type'] == 'text' }
92
- text_blocks.map { |c| c['text'] }.join
49
+ build_base_payload(chat_messages, temperature, model).tap do |payload|
50
+ Anthropic::Chat.add_optional_fields(payload, system_content:, tools:)
51
+ end
93
52
  end
94
53
 
95
- def build_message(data, content, tool_use)
96
- Message.new(
97
- role: :assistant,
98
- content: content,
99
- tool_calls: parse_tool_calls(tool_use),
100
- input_tokens: data.dig('usage', 'input_tokens'),
101
- output_tokens: data.dig('usage', 'output_tokens'),
102
- model_id: data['model']
103
- )
54
+ def build_base_payload(chat_messages, temperature, model)
55
+ {
56
+ anthropic_version: 'bedrock-2023-05-31',
57
+ messages: chat_messages.map { |msg| format_message(msg) },
58
+ temperature: temperature,
59
+ max_tokens: RubyLLM.models.find(model)&.max_tokens || 4096
60
+ }
104
61
  end
105
62
  end
106
63
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ module Bedrock
6
+ # Media handling methods for the Bedrock API integration
7
+ module Media
8
+ extend Anthropic::Media
9
+
10
+ module_function
11
+
12
+ def format_content(content)
13
+ return [Anthropic::Media.format_text(content)] unless content.is_a?(Content)
14
+
15
+ parts = []
16
+ parts << Anthropic::Media.format_text(content.text) if content.text
17
+
18
+ content.attachments.each do |attachment|
19
+ case attachment
20
+ when Attachments::Image
21
+ parts << format_image(attachment)
22
+ when Attachments::PDF
23
+ parts << format_pdf(attachment)
24
+ else
25
+ raise "Unsupported attachment type: #{attachment.class}"
26
+ end
27
+ end
28
+
29
+ parts
30
+ end
31
+
32
+ def format_image(image)
33
+ {
34
+ type: 'image',
35
+ source: {
36
+ type: 'base64',
37
+ media_type: image.mime_type,
38
+ data: image.encoded
39
+ }
40
+ }
41
+ end
42
+
43
+ def format_pdf(pdf)
44
+ {
45
+ type: 'document',
46
+ source: {
47
+ type: 'base64',
48
+ media_type: pdf.mime_type,
49
+ data: pdf.encoded
50
+ }
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -5,15 +5,14 @@ module RubyLLM
5
5
  module Bedrock
6
6
  # Models methods for the AWS Bedrock API implementation
7
7
  module Models
8
- def list_models
9
- @connection = nil # reset connection since base url is different
10
- @api_base = "https://bedrock.#{RubyLLM.config.bedrock_region}.amazonaws.com"
11
- full_models_url = "#{@api_base}/#{models_url}"
12
- signature = sign_request(full_models_url, method: :get)
13
- response = connection.get(models_url) do |req|
8
+ def list_models(connection:)
9
+ config = connection.config
10
+ mgmt_api_base = "https://bedrock.#{config.bedrock_region}.amazonaws.com"
11
+ full_models_url = "#{mgmt_api_base}/#{models_url}"
12
+ signature = sign_request(full_models_url, config: config, method: :get)
13
+ response = connection.get(full_models_url) do |req|
14
14
  req.headers.merge! signature.headers
15
15
  end
16
- @connection = nil # reset connection since base url is different
17
16
 
18
17
  parse_list_models_response(response, slug, capabilities)
19
18
  end
@@ -25,65 +24,58 @@ module RubyLLM
25
24
  end
26
25
 
27
26
  def parse_list_models_response(response, slug, capabilities)
28
- data = response.body['modelSummaries'] || []
29
- data.filter { |model| model['modelId'].include?('claude') }
30
- .map { |model| create_model_info(model, slug, capabilities) }
31
- end
27
+ models = Array(response.body['modelSummaries'])
32
28
 
33
- def create_model_info(model, slug, capabilities)
34
- model_id = model['modelId']
35
- ModelInfo.new(
36
- **base_model_attributes(model_id, model, slug),
37
- **capability_attributes(model_id, capabilities),
38
- **pricing_attributes(model_id, capabilities),
39
- metadata: build_metadata(model)
40
- )
41
- end
29
+ # Filter to include only models we care about
30
+ models.select { |m| m['modelId'].include?('claude') }.map do |model_data|
31
+ model_id = model_data['modelId']
42
32
 
43
- def base_model_attributes(model_id, model, slug)
44
- {
45
- id: model_id_with_prefix(model_id, model),
46
- created_at: nil,
47
- display_name: model['modelName'] || capabilities.format_display_name(model_id),
48
- provider: slug
49
- }
33
+ ModelInfo.new(
34
+ id: model_id_with_region(model_id, model_data),
35
+ name: model_data['modelName'] || capabilities.format_display_name(model_id),
36
+ provider: slug,
37
+ family: capabilities.model_family(model_id),
38
+ created_at: nil,
39
+ context_window: capabilities.context_window_for(model_id),
40
+ max_output_tokens: capabilities.max_tokens_for(model_id),
41
+ modalities: capabilities.modalities_for(model_id),
42
+ capabilities: capabilities.capabilities_for(model_id),
43
+ pricing: capabilities.pricing_for(model_id),
44
+ metadata: {
45
+ provider_name: model_data['providerName'],
46
+ inference_types: model_data['inferenceTypesSupported'] || [],
47
+ streaming_supported: model_data['responseStreamingSupported'] || false,
48
+ input_modalities: model_data['inputModalities'] || [],
49
+ output_modalities: model_data['outputModalities'] || []
50
+ }
51
+ )
52
+ end
50
53
  end
51
54
 
52
- def model_id_with_prefix(model_id, model)
53
- return model_id unless model['inferenceTypesSupported']&.include?('INFERENCE_PROFILE')
54
- return model_id if model['inferenceTypesSupported']&.include?('ON_DEMAND')
55
-
56
- "us.#{model_id}"
57
- end
55
+ # Simple test-friendly method that only sets the ID
56
+ def create_model_info(model_data, slug, _capabilities)
57
+ model_id = model_data['modelId']
58
58
 
59
- def capability_attributes(model_id, capabilities)
60
- {
61
- context_window: capabilities.context_window_for(model_id),
62
- max_tokens: capabilities.max_tokens_for(model_id),
63
- type: capabilities.model_type(model_id),
64
- family: capabilities.model_family(model_id).to_s,
65
- supports_vision: capabilities.supports_vision?(model_id),
66
- supports_functions: capabilities.supports_functions?(model_id),
67
- supports_json_mode: capabilities.supports_json_mode?(model_id)
68
- }
59
+ ModelInfo.new(
60
+ id: model_id_with_region(model_id, model_data),
61
+ name: model_data['modelName'] || model_id,
62
+ provider: slug,
63
+ family: 'claude',
64
+ created_at: nil,
65
+ context_window: 200_000,
66
+ max_output_tokens: 4096,
67
+ modalities: { input: ['text'], output: ['text'] },
68
+ capabilities: [],
69
+ pricing: {},
70
+ metadata: {}
71
+ )
69
72
  end
70
73
 
71
- def pricing_attributes(model_id, capabilities)
72
- {
73
- input_price_per_million: capabilities.input_price_for(model_id),
74
- output_price_per_million: capabilities.output_price_for(model_id)
75
- }
76
- end
74
+ def model_id_with_region(model_id, model_data)
75
+ return model_id unless model_data['inferenceTypesSupported']&.include?('INFERENCE_PROFILE')
76
+ return model_id if model_data['inferenceTypesSupported']&.include?('ON_DEMAND')
77
77
 
78
- def build_metadata(model)
79
- {
80
- provider_name: model['providerName'],
81
- customizations_supported: model['customizationsSupported'] || [],
82
- inference_configurations: model['inferenceTypesSupported'] || [],
83
- response_streaming_supported: model['responseStreamingSupported'] || false,
84
- input_modalities: model['inputModalities'] || [],
85
- output_modalities: model['outputModalities'] || []
86
- }
78
+ "us.#{model_id}"
87
79
  end
88
80
  end
89
81
  end
@@ -29,6 +29,22 @@ module RubyLLM
29
29
  "model/#{@model_id}/invoke-with-response-stream"
30
30
  end
31
31
 
32
+ def stream_response(connection, payload, &block)
33
+ signature = sign_request("#{connection.connection.url_prefix}#{stream_url}", config: connection.config,
34
+ payload:)
35
+ accumulator = StreamAccumulator.new
36
+
37
+ connection.post stream_url, payload do |req|
38
+ req.headers.merge! build_headers(signature.headers, streaming: block_given?)
39
+ req.options.on_data = handle_stream do |chunk|
40
+ accumulator.add chunk
41
+ block.call chunk
42
+ end
43
+ end
44
+
45
+ accumulator.to_message
46
+ end
47
+
32
48
  def handle_stream(&block)
33
49
  buffer = String.new
34
50
  proc do |chunk, _bytes, env|
@@ -13,27 +13,16 @@ module RubyLLM
13
13
  extend Bedrock::Streaming
14
14
  extend Bedrock::Models
15
15
  extend Bedrock::Signing
16
-
17
- # This provider currently only supports Anthropic models, so the tools/media implementation is shared
18
- extend Anthropic::Media
16
+ extend Bedrock::Media
19
17
  extend Anthropic::Tools
20
18
 
21
19
  module_function
22
20
 
23
- def api_base
24
- @api_base ||= "https://bedrock-runtime.#{RubyLLM.config.bedrock_region}.amazonaws.com"
25
- end
26
-
27
- def post(url, payload)
28
- signature = sign_request("#{connection.url_prefix}#{url}", payload:)
29
- connection.post url, payload do |req|
30
- req.headers.merge! build_headers(signature.headers, streaming: block_given?)
31
-
32
- yield req if block_given?
33
- end
21
+ def api_base(config)
22
+ "https://bedrock-runtime.#{config.bedrock_region}.amazonaws.com"
34
23
  end
35
24
 
36
- def parse_error(response) # rubocop:disable Metrics/MethodLength
25
+ def parse_error(response)
37
26
  return if response.body.empty?
38
27
 
39
28
  body = try_parse_json(response.body)
@@ -49,25 +38,25 @@ module RubyLLM
49
38
  end
50
39
  end
51
40
 
52
- def sign_request(url, method: :post, payload: nil)
53
- signer = create_signer
54
- request = build_request(url, method:, payload:)
41
+ def sign_request(url, config:, method: :post, payload: nil)
42
+ signer = create_signer(config)
43
+ request = build_request(url, config:, method:, payload:)
55
44
  signer.sign_request(request)
56
45
  end
57
46
 
58
- def create_signer
47
+ def create_signer(config)
59
48
  Signing::Signer.new({
60
- access_key_id: RubyLLM.config.bedrock_api_key,
61
- secret_access_key: RubyLLM.config.bedrock_secret_key,
62
- session_token: RubyLLM.config.bedrock_session_token,
63
- region: RubyLLM.config.bedrock_region,
49
+ access_key_id: config.bedrock_api_key,
50
+ secret_access_key: config.bedrock_secret_key,
51
+ session_token: config.bedrock_session_token,
52
+ region: config.bedrock_region,
64
53
  service: 'bedrock'
65
54
  })
66
55
  end
67
56
 
68
- def build_request(url, method: :post, payload: nil)
57
+ def build_request(url, config:, method: :post, payload: nil)
69
58
  {
70
- connection: connection,
59
+ connection: connection(config),
71
60
  http_method: method,
72
61
  url: url || completion_url,
73
62
  body: payload ? JSON.generate(payload, ascii_only: false) : nil
@@ -114,8 +114,6 @@ module RubyLLM
114
114
  }
115
115
  }.freeze
116
116
 
117
- private
118
-
119
117
  # Default input price when model family can't be determined
120
118
  # @return [Float] the default input price
121
119
  def default_input_price
@@ -133,6 +131,41 @@ module RubyLLM
133
131
  def default_cache_hit_price
134
132
  0.07 # Default to chat cache hit price
135
133
  end
134
+
135
+ def modalities_for(_model_id)
136
+ {
137
+ input: ['text'],
138
+ output: ['text']
139
+ }
140
+ end
141
+
142
+ def capabilities_for(model_id)
143
+ capabilities = ['streaming']
144
+
145
+ # Function calling for chat models
146
+ capabilities << 'function_calling' if model_id.match?(/deepseek-chat/)
147
+
148
+ capabilities
149
+ end
150
+
151
+ def pricing_for(model_id)
152
+ family = model_family(model_id)
153
+ prices = PRICES.fetch(family, { input_miss: default_input_price, output: default_output_price })
154
+
155
+ standard_pricing = {
156
+ input_per_million: prices[:input_miss],
157
+ output_per_million: prices[:output]
158
+ }
159
+
160
+ # Add cached pricing if available
161
+ standard_pricing[:cached_input_per_million] = prices[:input_hit] if prices[:input_hit]
162
+
163
+ {
164
+ text_tokens: {
165
+ standard: standard_pricing
166
+ }
167
+ }
168
+ end
136
169
  end
137
170
  end
138
171
  end
@@ -9,13 +9,13 @@ module RubyLLM
9
9
 
10
10
  module_function
11
11
 
12
- def api_base
12
+ def api_base(_config)
13
13
  'https://api.deepseek.com'
14
14
  end
15
15
 
16
- def headers
16
+ def headers(config)
17
17
  {
18
- 'Authorization' => "Bearer #{RubyLLM.config.deepseek_api_key}"
18
+ 'Authorization' => "Bearer #{config.deepseek_api_key}"
19
19
  }
20
20
  end
21
21