Module: Ask::ModelsDevParser

Defined in:
lib/ask/models.rb

Overview

Parses raw models.dev API response JSON into ModelInfo objects. Extracted into a module for independent unit testing.

Constant Summary collapse

PROVIDER_MAP =

Maps models.dev provider keys to ask-rb provider slugs.

{
  "openai" => "openai",
  "anthropic" => "anthropic",
  "google" => "gemini",
  "google-vertex" => "vertexai",
  "amazon-bedrock" => "bedrock",
  "deepseek" => "deepseek",
  "mistral" => "mistral",
  "openrouter" => "openrouter",
  "perplexity" => "perplexity",
  "xai" => "xai",
  "github" => "github"
}.freeze
INPUT_MODALITIES =
%w[text image audio pdf video file].freeze
OUTPUT_MODALITIES =
%w[text image audio video embeddings moderation].freeze

Class Method Summary collapse

Class Method Details

.build_model(model_data, provider_slug, provider_key) ⇒ Ask::ModelInfo

Build a Ask::ModelInfo from a single model entry in the models.dev response.

Parameters:

  • model_data (Hash)

    the model data from the API

  • provider_slug (String)

    normalized provider slug

  • provider_key (String)

    original provider key from the API

Returns:



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ask/models.rb', line 168

def build_model(model_data, provider_slug, provider_key)
  modalities = normalize_modalities(model_data["modalities"])
  capabilities = extract_capabilities(model_data, modalities)
  pricing = build_pricing(model_data["cost"])
  created_date = [model_data["release_date"], model_data["last_updated"]]
                 .find { |v| v && !v.to_s.strip.empty? }

  ModelInfo.new(
    id: model_data["id"],
    name: model_data["name"] || model_data["id"],
    provider: provider_slug,
    family: model_data["family"],
    capabilities: capabilities,
    context_window: model_data.dig("limit", "context"),
    max_output_tokens: model_data.dig("limit", "output"),
    modalities: modalities,
    pricing: pricing,
    knowledge_cutoff: parse_date(model_data["knowledge"]),
    created_at: created_date,
    metadata: {
      source: "models.dev",
      provider_id: provider_key,
      open_weights: model_data["open_weights"],
      status: model_data["status"],
      reasoning_options: model_data["reasoning_options"]
    }.compact
  )
end

.build_pricing(cost) ⇒ Hash

Build a pricing hash from raw cost data.

Parameters:

  • cost (Hash, nil)

    cost object from the API

Returns:

  • (Hash)


224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/ask/models.rb', line 224

def build_pricing(cost)
  return {} unless cost

  text_standard = {
    input_per_million: cost["input"],
    output_per_million: cost["output"],
    cache_read_input_per_million: cost["cache_read"],
    cache_write_input_per_million: cost["cache_write"],
    reasoning_output_per_million: cost["reasoning"]
  }.compact

  audio_standard = {
    input_per_million: cost["input_audio"],
    output_per_million: cost["output_audio"]
  }.compact

  pricing = {}
  pricing[:text_tokens] = { standard: text_standard } if text_standard.any?
  pricing[:audio_tokens] = { standard: audio_standard } if audio_standard.any?
  pricing
end

.extract_capabilities(model_data, modalities) ⇒ Array<String>

Extract capability strings from model data and modalities.

Parameters:

  • model_data (Hash)

    raw model data

  • modalities (Hash)

    normalized modalities

Returns:

  • (Array<String>)


212
213
214
215
216
217
218
219
# File 'lib/ask/models.rb', line 212

def extract_capabilities(model_data, modalities)
  caps = []
  caps << "function_calling" if model_data["tool_call"]
  caps << "structured_output" if model_data["structured_output"]
  caps << "reasoning" if model_data["reasoning"] || model_data["reasoning_options"]
  caps << "vision" if modalities[:input].intersect?(%w[image video pdf])
  caps.uniq
end

.normalize_modalities(modalities) ⇒ Hash{Symbol => Array<String>}

Returns normalized with known modality filters.

Parameters:

  • modalities (Hash, nil)

    raw modalities hash

Returns:

  • (Hash{Symbol => Array<String>})

    normalized with known modality filters



199
200
201
202
203
204
205
206
# File 'lib/ask/models.rb', line 199

def normalize_modalities(modalities)
  return { input: [], output: [] } unless modalities

  {
    input: Array(modalities["input"]).compact & INPUT_MODALITIES,
    output: Array(modalities["output"]).compact & OUTPUT_MODALITIES
  }
end

.parse(api_response) ⇒ Array<Ask::ModelInfo>

Parse a raw models.dev API response into Ask::ModelInfo objects.

Parameters:

  • api_response (Hash)

    the parsed JSON from models.dev/api.json

Returns:



151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ask/models.rb', line 151

def parse(api_response)
  api_response.flat_map do |provider_key, provider_data|
    provider_slug = PROVIDER_MAP[provider_key.to_s]
    next [] unless provider_slug

    models_data = provider_data.dig("models") || {}
    models_data.values.map do |model_data|
      build_model(model_data, provider_slug, provider_key.to_s)
    end
  end.compact
end

.parse_date(value) ⇒ Date?

Parse a date from a string, returning nil on failure.

Parameters:

  • value (String, Date, nil)

Returns:

  • (Date, nil)


249
250
251
252
253
254
255
256
# File 'lib/ask/models.rb', line 249

def parse_date(value)
  return nil if value.nil?
  return value if value.is_a?(Date)

  Date.parse(value.to_s)
rescue ArgumentError
  nil
end