Packages

Elixir client for the Mistral AI API with complete feature parity to the Python SDK.

Current section

Files

Jump to
mistralex_ai lib mistral_client api models.ex
Raw

lib/mistral_client/api/models.ex

defmodule MistralClient.API.Models do
@moduledoc """
Models API for the Mistral AI client.
This module provides functions for managing and retrieving information about
available models, including listing models, retrieving model details, and
managing fine-tuned models.
## Features
* List available models
* Retrieve model details
* Delete fine-tuned models
* Update model metadata
* Archive and unarchive models
* Model permission management
## Usage
# List all available models
{:ok, models} = MistralClient.API.Models.list()
# Get details for a specific model
{:ok, model} = MistralClient.API.Models.retrieve("mistral-large-latest")
# Delete a fine-tuned model
{:ok, _} = MistralClient.API.Models.delete("ft:mistral-small:my-org:custom-suffix")
"""
alias MistralClient.{Client, Models, Errors}
require Logger
@endpoint "/models"
@type model_id :: String.t()
@type update_options :: %{
name: String.t() | nil,
description: String.t() | nil,
metadata: map() | nil
}
@doc """
List all available models.
Returns a list of models that are available for use, including both
base models and fine-tuned models.
## Parameters
* `client` - HTTP client (optional, uses default if not provided)
## Examples
{:ok, models} = MistralClient.API.Models.list()
# Filter for specific model types
{:ok, models} = MistralClient.API.Models.list()
base_models = Enum.filter(models, &(&1.owned_by == "mistralai"))
"""
@spec list(Client.t() | nil) :: {:ok, Models.ModelList.t()} | {:error, Exception.t()}
def list(client \\ nil) do
client = client || Client.new()
case Client.request(client, :get, @endpoint) do
{:ok, response} ->
model_list = Models.ModelList.from_map(response)
{:ok, model_list}
{:error, _} = error ->
error
end
end
@doc """
Retrieve details for a specific model.
## Parameters
* `model_id` - The ID of the model to retrieve
* `client` - HTTP client (optional, uses default if not provided)
## Examples
{:ok, model} = MistralClient.API.Models.retrieve("mistral-large-latest")
{:ok, model} = MistralClient.API.Models.retrieve("ft:mistral-small:my-org:custom-suffix")
"""
@spec retrieve(model_id(), Client.t() | nil) ::
{:ok, Models.BaseModelCard.t() | Models.FTModelCard.t()} | {:error, Exception.t()}
def retrieve(model_id, client \\ nil) do
client = client || Client.new()
case validate_model_id(model_id) do
:ok ->
path = "#{@endpoint}/#{model_id}"
case Client.request(client, :get, path) do
{:ok, response} ->
model = parse_model_response(response)
{:ok, model}
{:error, _} = error ->
error
end
{:error, _} = error ->
error
end
end
@doc """
Delete a fine-tuned model.
You can only delete fine-tuned models that you own. Base models cannot be deleted.
## Parameters
* `model_id` - The ID of the fine-tuned model to delete
* `client` - HTTP client (optional, uses default if not provided)
## Examples
{:ok, result} = MistralClient.API.Models.delete("ft:mistral-small:my-org:custom-suffix")
"""
@spec delete(model_id(), Client.t() | nil) ::
{:ok, Models.DeleteModelOut.t()} | {:error, Exception.t()}
def delete(model_id, client \\ nil) when is_binary(model_id) do
client = client || Client.new()
case validate_model_id(model_id) do
:ok ->
path = "#{@endpoint}/#{model_id}"
case Client.request(client, :delete, path) do
{:ok, response} ->
delete_result = Models.DeleteModelOut.from_map(response)
{:ok, delete_result}
{:error, _} = error ->
error
end
{:error, _} = error ->
error
end
end
@doc """
Update metadata for a fine-tuned model.
## Parameters
* `model_id` - The ID of the model to update
* `updates` - Map of fields to update
* `client` - HTTP client (optional, uses default if not provided)
## Update Options
* `:name` - New name for the model
* `:description` - New description for the model
* `:metadata` - Additional metadata
## Examples
{:ok, model} = MistralClient.API.Models.update("ft:mistral-small:my-org:custom-suffix", %{
name: "My Custom Model",
description: "A model fine-tuned for my specific use case"
})
"""
@spec update(model_id(), update_options(), Client.t() | nil) ::
{:ok, Models.BaseModelCard.t() | Models.FTModelCard.t()} | {:error, Exception.t()}
def update(model_id, updates, client \\ nil) when is_binary(model_id) and is_map(updates) do
client = client || Client.new()
with :ok <- validate_model_id(model_id),
{:ok, request_body} <- build_update_body(updates) do
path = "#{@endpoint}/#{model_id}"
case Client.request(client, :patch, path, request_body) do
{:ok, response} ->
model = parse_model_response(response)
{:ok, model}
{:error, _} = error ->
error
end
end
end
@doc """
Archive a model.
Archived models are not available for inference but are still accessible
for management operations.
## Parameters
* `model_id` - The ID of the model to archive
* `client` - HTTP client (optional, uses default if not provided)
## Examples
{:ok, model} = MistralClient.API.Models.archive("ft:mistral-small:my-org:custom-suffix")
"""
@spec archive(model_id(), Client.t() | nil) ::
{:ok, Models.ArchiveFTModelOut.t()} | {:error, Exception.t()}
def archive(model_id, client \\ nil) when is_binary(model_id) do
client = client || Client.new()
case validate_model_id(model_id) do
:ok ->
path = "#{@endpoint}/#{model_id}/archive"
case Client.request(client, :post, path) do
{:ok, response} ->
archive_result = Models.ArchiveFTModelOut.from_map(response)
{:ok, archive_result}
{:error, _} = error ->
error
end
{:error, _} = error ->
error
end
end
@doc """
Unarchive a model.
Makes an archived model available for inference again.
## Parameters
* `model_id` - The ID of the model to unarchive
* `client` - HTTP client (optional, uses default if not provided)
## Examples
{:ok, model} = MistralClient.API.Models.unarchive("ft:mistral-small:my-org:custom-suffix")
"""
@spec unarchive(model_id(), Client.t() | nil) ::
{:ok, Models.UnarchiveFTModelOut.t()} | {:error, Exception.t()}
def unarchive(model_id, client \\ nil) when is_binary(model_id) do
client = client || Client.new()
case validate_model_id(model_id) do
:ok ->
path = "#{@endpoint}/#{model_id}/unarchive"
case Client.request(client, :post, path) do
{:ok, response} ->
unarchive_result = Models.UnarchiveFTModelOut.from_map(response)
{:ok, unarchive_result}
{:error, _} = error ->
error
end
{:error, _} = error ->
error
end
end
@doc """
Check if a model exists and is available.
## Parameters
* `model_id` - The ID of the model to check
* `client` - HTTP client (optional, uses default if not provided)
## Examples
true = MistralClient.API.Models.exists?("mistral-large-latest")
false = MistralClient.API.Models.exists?("non-existent-model")
"""
@spec exists?(model_id(), Client.t() | nil) :: boolean()
def exists?(model_id, client \\ nil) when is_binary(model_id) do
case retrieve(model_id, client) do
{:ok, _model} -> true
{:error, %Errors.NotFoundError{}} -> false
{:error, _} -> false
end
end
@doc """
Filter models by type.
## Parameters
* `models` - List of models to filter
* `filter_type` - Type of filter to apply
## Filter Types
* `:base` - Base models owned by Mistral AI
* `:fine_tuned` - Fine-tuned models
* `:owned` - Models owned by the current user/organization
## Examples
{:ok, all_models} = MistralClient.API.Models.list()
base_models = MistralClient.API.Models.filter_models(all_models, :base)
fine_tuned = MistralClient.API.Models.filter_models(all_models, :fine_tuned)
"""
@spec filter_models(list(Models.BaseModelCard.t() | Models.FTModelCard.t()), atom()) ::
list(Models.BaseModelCard.t() | Models.FTModelCard.t())
def filter_models(models, :base) when is_list(models) do
Enum.filter(models, fn
%Models.BaseModelCard{} -> true
%Models.FTModelCard{} -> false
end)
end
def filter_models(models, :fine_tuned) when is_list(models) do
Enum.filter(models, fn
%Models.FTModelCard{} -> true
%Models.BaseModelCard{} -> false
end)
end
def filter_models(models, :owned) when is_list(models) do
Enum.filter(models, fn
%Models.BaseModelCard{owned_by: owned_by} -> owned_by != "mistralai"
%Models.FTModelCard{owned_by: owned_by} -> owned_by != "mistralai"
end)
end
def filter_models(models, _filter_type) when is_list(models) do
models
end
# Private functions
defp parse_model_response(%{"job" => _} = response) do
Models.FTModelCard.from_map(response)
end
defp parse_model_response(response) do
Models.BaseModelCard.from_map(response)
end
defp validate_model_id(model_id) when is_binary(model_id) and byte_size(model_id) > 0 do
:ok
end
defp validate_model_id(_model_id) do
{:error,
Errors.ValidationError.exception(
message: "Model ID must be a non-empty string",
field: "model_id"
)}
end
defp build_update_body(updates) when is_map(updates) do
allowed_fields = [:name, :description, :metadata]
filtered_updates =
updates
|> Enum.filter(fn {key, _value} -> key in allowed_fields end)
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
|> Map.new()
if map_size(filtered_updates) == 0 do
{:error,
Errors.ValidationError.exception(
message: "At least one valid field must be provided for update",
field: "updates"
)}
else
case validate_update_fields(filtered_updates) do
:ok -> {:ok, filtered_updates}
{:error, _} = error -> error
end
end
end
defp validate_update_fields(updates) do
cond do
Map.has_key?(updates, :name) &&
(not is_binary(updates.name) || String.trim(updates.name) == "") ->
{:error,
Errors.ValidationError.exception(
message: "Name must be a non-empty string",
field: "name"
)}
Map.has_key?(updates, :description) && not is_binary(updates.description) ->
{:error,
Errors.ValidationError.exception(
message: "Description must be a string",
field: "description"
)}
Map.has_key?(updates, :metadata) && not is_map(updates.metadata) ->
{:error,
Errors.ValidationError.exception(
message: "Metadata must be a map",
field: "metadata"
)}
true ->
:ok
end
end
end