Class: Labimotion::AiModels

Inherits:
Object
  • Object
show all
Defined in:
lib/labimotion/libs/ai_models.rb

Overview

Fetches the LIVE model catalogue from an OpenAI-compatible AI provider (the KIT KI-Toolbox, https://ki-toolbox.scc.kit.edu, by default) using the user's OWN API key. "My LabIMotion" offers the result in its model dropdown, so a user does not depend on the static pick-list in config/labimotion_ai.yml (:models), which goes stale whenever the provider changes its line-up.

Only a PERSONAL key is ever used here: the shared server key must never be sent to a user-supplied provider URL, and a keyless user's choice is restricted to the admin allowlist anyway (Chemotion::ProfileAPI#labimotion_ai_admin_model_ids), so there would be nothing for them to pick out of a live list.

Endpoint resolution mirrors Labimotion::AiTemplate: a personal :base_url (and :api_path) is honored only alongside a personal key and is SSRF-validated by Labimotion::AiEgressGuard before any request; otherwise the server-wide config applies.

Examples:

Labimotion::AiModels.new(api_key: key).call
# => { models: [{ id: 'azure.gpt-4.1-mini', label: 'GPT-4.1 mini' }, ...],
#      endpoint: 'https://ki-toolbox.scc.kit.edu/api/v1/models' }

Defined Under Namespace

Classes: Error

Constant Summary collapse

DEFAULT_BASE_URL =
'https://ki-toolbox.scc.kit.edu'
DEFAULT_API_PATH =
'/api/v1/chat/completions'
FALLBACK_PATHS =

List endpoints tried after the one derived from the configured chat-completions path: Open WebUI (what KI-Toolbox runs) serves /api/models, the OpenAI standard is /v1/models. The first that answers with a usable list wins.

['/api/models', '/v1/models', '/api/v1/models'].freeze
REQUEST_TIMEOUT =
20
NON_CHAT_PATTERN =

Model ids/names that are not chat models. KI-Toolbox lists embedding, reranking, speech and image models next to the chat ones; offering those in the dropdown would only produce puzzling failures when a template is generated.

/
  embed | rerank | whisper | \btts\b | \bstt\b | flux | stable-?diffusion |
  dall-?e | \bbge\b | moderation
/xi
UNAUTHORIZED_CODES =

A provider answering these has a working endpoint but not the caller's key.

[401, 403].freeze
MISSING_KEY_ERROR =
'Add your personal API key first — the model list is fetched with your own key.'

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url: nil, api_path: nil) ⇒ AiModels

Returns a new instance of AiModels.



51
52
53
54
55
# File 'lib/labimotion/libs/ai_models.rb', line 51

def initialize(api_key:, base_url: nil, api_path: nil)
  @api_key = api_key.to_s.strip
  @base_url_override = base_url.to_s.strip.presence
  @api_path_override = api_path.to_s.strip.presence
end

Instance Method Details

#callHash

Returns { models: [{ id:, label: }], endpoint: String }.

Returns:

  • (Hash)

    { models: [{ id:, label: }], endpoint: String }

Raises:

  • (Error)

    with a user-facing message when no list could be read



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/labimotion/libs/ai_models.rb', line 59

def call
  raise Error, MISSING_KEY_ERROR if @api_key.blank?

  statuses = {}
  candidate_paths.each do |path|
    url = "#{base_url}#{path}"
    response = get(url)
    statuses[path] = response&.code
    models = parse_models(response)
    return { models: models, endpoint: url } if models.present?
  end
  raise Error, failure_message(statuses)
end