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



316
317
318
319
320
# File 'lib/ollama_chat/model_handling.rb', line 316

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



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/ollama_chat/model_handling.rb', line 333

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)&.value || current_model
    else
      cli_model || current_model
    end
ensure
  connect_message(model, ollama.base_url)
end

#copy_model_options_from_sessionObject (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.



136
137
138
139
140
141
# File 'lib/ollama_chat/model_handling.rb', line 136

def copy_model_options_from_session
  model_name    = @model
  model_options = get_session_model_options
  store_model_options(model_name, model_options)
  STDOUT.puts "Default model options of #{bold{model_name}} were copied from session model options."
end

#copy_model_options_to_sessionObject (private)

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



145
146
147
148
149
150
# File 'lib/ollama_chat/model_handling.rb', line 145

def copy_model_options_to_session
  model_name = @model
  stored_model_options = get_stored_model_options(model_name)
  session.update(model_options: stored_model_options)
  STDOUT.puts "Default model options of #{bold{model_name}} were copied to session model options."
end

#edit_model_options(model_name) ⇒ 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)

    the name of the model whose options are to be edited.



109
110
111
112
113
114
115
116
117
# File 'lib/ollama_chat/model_handling.rb', line 109

def edit_model_options(model_name)
  model_options      = get_stored_model_options(model_name)
  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)
rescue JSON::ParserError => e
  log(:error, "Caught in #{__method__} #{e.class}: #{e}", 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



122
123
124
125
126
127
128
129
130
131
# File 'lib/ollama_chat/model_handling.rb', line 122

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).compact
  session.update(model_options:)
  self
rescue JSON::ParserError => e
  log(:error, "Caught in #{__method__} #{e.class}: #{e}", 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



80
81
82
83
84
85
# File 'lib/ollama_chat/model_handling.rb', line 80

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



65
66
67
# File 'lib/ollama_chat/model_handling.rb', line 65

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



58
59
60
# File 'lib/ollama_chat/model_handling.rb', line 58

def get_session_model_options
  session.model_options.to_h.symbolize_keys_recursive
end

#get_stored_model_options(model_name) ⇒ 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
# File 'lib/ollama_chat/model_handling.rb', line 40

def get_stored_model_options(model_name)
  models::ModelOptions.where(model_name:).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



72
73
74
# File 'lib/ollama_chat/model_handling.rb', line 72

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



159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ollama_chat/model_handling.rb', line 159

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



212
213
214
215
216
217
218
# File 'lib/ollama_chat/model_handling.rb', line 212

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:



230
231
232
233
234
235
236
237
238
239
# File 'lib/ollama_chat/model_handling.rb', line 230

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



176
177
178
179
# File 'lib/ollama_chat/model_handling.rb', line 176

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:



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/ollama_chat/model_handling.rb', line 192

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

#store_model_options(model_name, model_options) ⇒ 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



92
93
94
95
96
97
98
99
100
101
# File 'lib/ollama_chat/model_handling.rb', line 92

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

#stored_model_options_exist?(model_name) ⇒ 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:



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

def stored_model_options_exist?(model_name)
  models::ModelOptions.where(model_name:).first
end

#use_model(model = nil, keep_options: false) ⇒ 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 is retained

  • keep_options (Boolean) (defaults to: false)

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

Returns:



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/ollama_chat/model_handling.rb', line 257

def use_model(model = nil, keep_options: false)
  old_model = @model

  if model.nil?
    @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

  if old_model != @model
    default_model_options = get_default_model_options
    session_model_options = get_session_model_options
    unless stored_model_options_exist?(@model)
      store_model_options(@model, default_model_options)
    end
    stored_model_options = get_stored_model_options(@model)
    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
      if confirm?(
          prompt: "❓ Overwrite session model options with defaults? (y/n) ", yes: /\Ay/i
        )
      then
        session.update(model_options: stored_model_options)
      end
    end
  end

  @model_metadata
end