Module: OllamaChat::ModelHandling

Included in:
Chat
Defined in:
lib/ollama_chat/model_handling.rb

Overview

A module that provides functionality for managing Ollama models, including checking model availability, pulling models from remote servers, and handling model presence verification.

This module encapsulates the logic for interacting with Ollama models, ensuring that required models are available locally before attempting to use them in chat sessions. It handles both local model verification and remote model retrieval when necessary.

Examples:

Checking if a model is present

chat.model_present?('llama3.1')

Pulling a model from a remote server

chat.pull_model_from_remote('mistral')

Ensuring a model is available locally

chat.pull_model_unless_present('phi3', {})

Defined Under Namespace

Classes: ModelMetadata

Instance Method Summary collapse

Instance Method Details

#all_modelsArray<SearchUI::Wrapper> (private)

Retrieves a sorted list of all available Ollama models, enriched with size information and marked as favorites where applicable.

This method fetches the list of models from the Ollama server, sorts them alphabetically by name, and wraps each in a SearchUI::Wrapper for consistent display in the user interface.

Returns:

  • (Array<SearchUI::Wrapper>)

    a sorted list of available models with metadata



370
371
372
373
374
# File 'lib/ollama_chat/model_handling.rb', line 370

def all_models
  favs = all_favourited('model')
  ollama.tags.models.sort_by(&:name).
    map { |m| model_with_size(m, favourited: favs[m.name]) }
end

#choose_model(cli_model, current_model) ⇒ String (private)

The choose_model method selects a model from the available list based on CLI input or user interaction. It processes the provided CLI model parameter to determine if a regex selector is used, filters the models accordingly, and prompts the user to choose from the filtered list if needed. The method ensures that a model is selected and displays a connection message with the chosen model and base URL.

Parameters:

  • cli_model (String)

    the model name or pattern provided via CLI

  • current_model (String)

    the fallback model if selection fails

Returns:

  • (String)

    the selected model name



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/ollama_chat/model_handling.rb', line 387

def choose_model(cli_model, current_model)
  selector = if cli_model =~ /\A\?+(.*)\z/
               cli_model = ''
               Regexp.new($1)
             end
  models = all_models
  selector and models = models.select { _1.value =~ selector }
  model =
    if models.size == 1
      models.first.value
    elsif cli_model == ''
      choose_entry(
        models,
        prompt: "Which digital oracle shall we consult? %s"
      )&.value || current_model
    else
      cli_model || current_model
    end
ensure
  connect_message(model, ollama.base_url)
end

#choose_profile_for_model(model_name) ⇒ String? (private)

Presents an interactive list of stored configuration profiles for the specified model and prompts the user to select one.

If only one profile exists for the model, it is returned immediately without prompting. If the user cancels the selection or chooses [EXIT], nil is returned.

Parameters:

  • model_name (String)

    the name of the model whose profiles are to be listed

Returns:

  • (String, nil)

    the selected profile name, or nil if none was chosen



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ollama_chat/model_handling.rb', line 184

def choose_profile_for_model(model_name)
  profiles = models::ModelOptions.where(model_name:).order(:profile).map(&:profile)
  profiles.size < 2 and return profiles.first
  profiles = [ '[EXIT]' ] + profiles
  case chosen = choose_entry(profiles, prompt: "Choose profile for #{bold{model_name}}: %s")
  when '[EXIT]', nil
    STDOUT.puts "Cancelled."
    return
  else
    chosen
  end
end

#copy_model_options_from_session(model_name = nil, profile: nil) ⇒ Object (private)

This method retrieves the options stored for the current session and updates the active model options to match, ensuring the model behavior aligns with the session's specific configuration.

Parameters:

  • model_name (String, nil) (defaults to: nil)

    the model to use; defaults to @model

  • profile (String, nil) (defaults to: nil)

    the profile context for model options



154
155
156
157
158
159
160
# File 'lib/ollama_chat/model_handling.rb', line 154

def copy_model_options_from_session(model_name = nil, profile: nil)
  profile       ||= 'default'
  model_name      ||= @model
  model_options   = get_session_model_options
  store_model_options(model_name, model_options, profile:)
  STDOUT.puts "Model options #{italic{profile}} for #{bold{model_name}} were copied from session model options."
end

#copy_model_options_to_session(model_name = nil, profile: nil) ⇒ Object (private)

Resets the session's model options to match the stored defaults for the specified model.

Parameters:

  • model_name (String, nil) (defaults to: nil)

    the model to use; defaults to @model

  • profile (String, nil) (defaults to: nil)

    the profile context for model options



167
168
169
170
171
172
173
# File 'lib/ollama_chat/model_handling.rb', line 167

def copy_model_options_to_session(model_name = nil, profile: nil)
  profile              ||= 'default'
  model_name             ||= @model
  stored_model_options   = get_stored_model_options(model_name, profile:)
  session.update(model_options: stored_model_options)
  STDOUT.puts "Model options #{italic{profile}} of #{bold{model_name}} were copied to session model options."
end

#edit_model_options(model_name = nil, profile: nil) ⇒ Object (private)

The edit_model_options method retrieves the current options for the specified model, presents them to the user for editing, and returns a new Ollama::Options instance based on the edited configuration.

Parameters:

  • model_name (String) (defaults to: nil)

    the name of the model whose options are to be edited, defaults to @model.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ollama_chat/model_handling.rb', line 112

def edit_model_options(model_name = nil, profile: nil)
  model_name         ||= @model
  profile            ||= 'default'
  model_options        = get_stored_model_options(model_name, profile:)
  model_options        = fill_up_model_options(model_options)
  model_options_json   = edit_text(JSON.pretty_generate(model_options))
  model_options        = JSON.load(model_options_json)
  store_model_options(model_name, model_options, profile:)
rescue JSON::ParserError => e
  log(
    :error,
    "Model handling error",
    data: { method: __method__, error_class: e.class, error_message: e.message },
    warn: true
  )
end

#edit_session_model_optionsself (private)

Presents the current session's model options to the user for editing.

Returns:

  • (self)

    the instance of the module



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ollama_chat/model_handling.rb', line 132

def edit_session_model_options
  model_options        = get_session_model_options
  model_options        = fill_up_model_options(model_options)
  model_options_json   = edit_text(JSON.pretty_generate(model_options))
  model_options        = (JSON.load(model_options_json).full? || {}).compact
  session.update(model_options:)
  self
rescue JSON::ParserError => e
  log(
    :error,
    "Model handling error",
    data: { method: __method__, error_class: e.class, error_message: e.message },
    warn: true
  )
end

#fill_up_model_options(model_options) ⇒ Ollama::Options (private)

Fills in missing keys in a model options hash using the attributes of Ollama::Options.

Parameters:

  • model_options (Hash)

    the hash containing the available model options

Returns:

  • (Ollama::Options)

    an Ollama::Options object containing all required keys



82
83
84
85
86
87
# File 'lib/ollama_chat/model_handling.rb', line 82

def fill_up_model_options(model_options)
  Ollama::Options.attributes.each_with_object(model_options) do |name, mo|
    mo[name] = model_options[name]
  end
  model_options
end

#get_default_model_optionsHash (private)

Retrieves the default model options from the application configuration.

Returns:

  • (Hash)

    the default model options as a hash



67
68
69
# File 'lib/ollama_chat/model_handling.rb', line 67

def get_default_model_options
  config.model.options.to_h
end

#get_session_model_optionsHash (private)

Retrieves the model options currently associated with the active session.

Returns:

  • (Hash)

    the session model options as a hash with symbolized keys



60
61
62
# File 'lib/ollama_chat/model_handling.rb', line 60

def get_session_model_options
  session.model_options.to_h.symbolize_keys_recursive
end

#get_stored_model_options(model_name, profile: nil) ⇒ Hash

Retrieves the stored model options from the database for a given model name.

Parameters:

  • model_name (String)

    the name of the model to look up

Returns:

  • (Hash)

    the model options as a hash with symbolized keys



40
41
42
43
44
# File 'lib/ollama_chat/model_handling.rb', line 40

def get_stored_model_options(model_name, profile: nil)
  profile ||= 'default'
  models::ModelOptions.where(model_name:, profile:).first&.options.
    to_h.symbolize_keys_recursive
end

#model_optionsOllama::Options (private)

Computes the current model options as an Ollama::Options object.

Returns:

  • (Ollama::Options)

    the current model options object



74
75
76
# File 'lib/ollama_chat/model_handling.rb', line 74

def model_options
  Ollama::Options[session.model_options.to_h.symbolize_keys_recursive]
end

#model_present?(model) ⇒ ModelMetadata, NilClass (private)

The model_present? method checks if the specified Ollama model is available.

Parameters:

  • model (String)

    the name of the Ollama model

Returns:

  • (ModelMetadata, NilClass)

    if the model is present, nil otherwise



204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ollama_chat/model_handling.rb', line 204

def model_present?(model)
  ollama.show(model:) do |md|
    return ModelMetadata.new(
      name:         model,
      system:       md.system,
      capabilities: md.capabilities,
      families:     md.details.families,
    )
  end
rescue Ollama::Errors::NotFoundError
  nil
end

#model_with_size(model, favourited: false) ⇒ Object (private)

The model_with_size method formats a model's size for display by creating a formatted string that includes the model name and its size in a human-readable format with appropriate units.

Parameters:

  • model (Object)

    the model object that has name and size attributes

Returns:

  • (Object)

    a result object with an overridden to_s method that combines the model name and formatted size



257
258
259
260
261
262
263
# File 'lib/ollama_chat/model_handling.rb', line 257

def model_with_size(model, favourited: false)
  formatted_size = Term::ANSIColor.bold {
    format_bytes(model.size)
  }
  display = prefix_favourite("#{model.name} #{formatted_size}", favourited)
  SearchUI::Wrapper.new(model.name, display:)
end

#prepare_model(model) ⇒ OllamaChat::ModelHandling::ModelMetadata (private)

Ensures the specified model is available locally and synchronizes the session's capability settings with the model's actual supported features.

This method performs a lazy-load check: it pulls the model if it's missing and then immediately validates and updates 'thinking' and 'tools' support to prevent invalid API requests.

Parameters:

  • model (String)

    the name of the model to prepare for use

Returns:



275
276
277
278
279
280
281
282
283
284
# File 'lib/ollama_chat/model_handling.rb', line 275

def prepare_model(model)
  @model_metadata = pull_model_unless_present(model)
  if think? && !@model_metadata.can?('thinking')
    think_mode.selected = 'disabled'
  end

  if tools_support.on? && !@model_metadata.can?('tools')
    tools_support.set false
  end
end

#pull_model_from_remote(model) ⇒ Object (private)

The pull_model_from_remote method attempts to retrieve a model from the remote server if it is not found locally.

Parameters:

  • model (String)

    the name of the model to be pulled



221
222
223
224
# File 'lib/ollama_chat/model_handling.rb', line 221

def pull_model_from_remote(model)
  STDOUT.puts "Model #{bold{model}} not found locally, attempting to pull it from remote now…"
  ollama.pull(model:)
end

#pull_model_unless_present(model) ⇒ ModelMetadata (private)

The pull_model_unless_present method ensures that a specified model is available on the Ollama server. It first checks if the model metadata exists locally; if not, it pulls the model from a remote source and verifies its presence again. If the model still cannot be found, it raises an UnknownModelError indicating the missing model name.

Parameters:

  • model (String)

    the name of the model to ensure is present

Returns:

Raises:



237
238
239
240
241
242
243
244
245
246
247
# File 'lib/ollama_chat/model_handling.rb', line 237

def pull_model_unless_present(model)
  if  = model_present?(model)
    return 
  else
    pull_model_from_remote(model)
    if  = model_present?(model)
      return 
    end
    raise OllamaChat::UnknownModelError, "unknown model named #{@model.inspect}"
  end
end

#reconfigure_model_options(profile:, keep_options:) ⇒ Object (private)

Reconfigures model options for the current model and profile.

Ensures that the session has the appropriate model options by:

  • Storing default options if none exist for the model/profile combination
  • Populating blank session options with stored or default values
  • Prompting to overwrite session options if they diverge from stored defaults (only when keep_options is false)

Parameters:

  • profile (String, nil)

    The profile context for model options storage



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/ollama_chat/model_handling.rb', line 295

def reconfigure_model_options(profile:, keep_options:)
  default_model_options = get_default_model_options
  session_model_options = get_session_model_options
  unless stored_model_options_exist?(@model, profile:)
    store_model_options(@model, default_model_options, profile:)
  end
  stored_model_options = get_stored_model_options(@model, profile:)
  if session_model_options.blank?
    if stored_model_options.present?
      session.update(model_options: stored_model_options)
    else
      store_model_options(@model, default_model_options)
      session.update(model_options: default_model_options)
    end
  elsif !keep_options && session_model_options != stored_model_options
    STDOUT.puts <<~EOT
        ⚠️ Session model options differ from defaults for model #@model!
        Session model options:
        #{JSON.pretty_generate(session_model_options)}
        Default model options:
        #{JSON.pretty_generate(stored_model_options)}
    EOT
    confirm?(
        prompt: "❓ Overwrite session model options with defaults? (y/n) ", yes: /\Ay/i
    ) and session.update(model_options: stored_model_options)
  end
end

#store_model_options(model_name, model_options, profile: nil) ⇒ Hash (private)

Stores or updates model options in the database for a specific model.

Parameters:

  • model_name (String)

    the name of the model to target

  • model_options (Hash, Ollama::Options)

    the options to persist

Returns:

  • (Hash)

    the updated model options hash



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ollama_chat/model_handling.rb', line 94

def store_model_options(model_name, model_options, profile: nil)
  profile ||= 'default'
  options   = model_options.to_h.symbolize_keys_recursive.compact
  mo        = nil
  if mo = stored_model_options_exist?(model_name, profile:)
    mo.update(options:)
  else
    mo = models::ModelOptions.create(model_name:, options:, profile:)
  end
  mo.options
end

#stored_model_options_exist?(model_name, profile: nil) ⇒ OllamaChat::Database::Models::ModelOptions? (private)

Checks if model options exist in the database for the given model name.

Parameters:

  • model_name (String)

    the name of the model to check

Returns:



52
53
54
55
# File 'lib/ollama_chat/model_handling.rb', line 52

def stored_model_options_exist?(model_name, profile: nil)
  profile ||= 'default'
  models::ModelOptions.where(model_name:, profile:).first
end

#use_model(model = nil, keep_options: false, profile: nil) ⇒ ModelMetadata (private)

The use_model method selects and sets the model to be used for the chat session.

It allows specifying a particular model or defaults to the current model. After selecting, it pulls the model metadata if necessary. If think? is true and the chosen model does not support thinking, the think mode selector is set to 'disabled'. If tools_support.on? is true and the chosen model does not support tools, tool support is disabled. Returns the metadata for the selected model.

Parameters:

  • model (String, nil) (defaults to: nil)

    the model name to use; if omitted, the current model can be selected

  • keep_options (Boolean) (defaults to: false)

    if true, session-specific model options are retained instead of reverting to model defaults.

Returns:



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/ollama_chat/model_handling.rb', line 339

def use_model(model = nil, keep_options: false, profile: nil)
  profile   ||= 'default'
  old_model   = @model

  if model.blank?
    @model = choose_model('', @model)
  else
    @model = choose_model(model, config.model.name)
  end

  if @model_metadata = model_present?(@model)
    session.update(current_model: @model)
  else
    session.update(current_model: nil)
  end

  old_model != @model and reconfigure_model_options(profile:, keep_options:)

  log(:info, "Model switched", data: { old_model:, new_model: @model, profile: })
  @model_metadata
end