defmodule Tangram do @moduledoc """ """ defmodule Model do @moduledoc """ """ defstruct [ :model, :log_queue, :tangram_url ] end defmodule ModelOptions do @moduledoc """ """ defstruct [ :tangram_url ] end defmodule PredictOptions do @moduledoc """ """ @derive Jason.Encoder defstruct [ :threshold, :compute_feature_contributions ] end defmodule LogPredictionArgs do @moduledoc """ """ @derive Jason.Encoder defstruct [ :identifier, :input, :options, :output ] end defmodule LogTrueValueArgs do @moduledoc """ """ @derive Jason.Encoder defstruct [ :identifier, :true_value ] end defmodule LogPredictionEvent do @moduledoc """ """ @derive Jason.Encoder defstruct [ :type, :model_id, :date, :identifier, :input, :options, :output ] end defmodule LogTrueValueEvent do @moduledoc """ """ @derive Jason.Encoder defstruct [ :type, :model_id, :date, :identifier, :true_value ] end @on_load {:init, 0} def init do sys_arch = to_string(:erlang.system_info(:system_architecture)) nif_path = cond do String.match?(sys_arch, ~r/x86_64-(pc|unknown)-linux-gnu/) -> "x86_64-unknown-linux-gnu/libtangram_elixir" # String.match?(sys_arch, ~r/(aarch64|arm)-(pc|unknown)-linux-gnu/) -> # "aarch64-unknown-linux-gnu/libtangram_elixir" String.match?(sys_arch, ~r/x86_64-(alpine|pc)-linux-musl/) -> "x86_64-unknown-linux-gnu/libtangram_elixir" # String.match?(sys_arch, ~r/aarch64-(alpine|pc)-linux-musl/) -> # "aarch64-unknown-linux-gnu/libtangram_elixir" String.match?(sys_arch, ~r/x86_64-apple-darwin[0-9]+\.[0-9]+\.[0-9]+/) -> "x86_64-apple-darwin/libtangram_elixir" # String.match?(sys_arch, ~r/(aarch64|arm)-apple-darwin[0-9]+\.[0-9]+\.[0-9]+/) -> # "aarch64-apple-darwin/libtangram_elixir" true -> raise "Tangram for Elixir does not yet support your combination of operating system and CPU architecture. Open an issue at https://github.com/tangramxyz/tangram/issues/new or email us at help@tangram.xyz to complain." end path = :filename.join(:code.priv_dir(:tangram), nif_path) :ok = :erlang.load_nif(path, 0) end @doc """ """ def load_model(data, options \\ nil) do model = _load_model(data) tangram_url = if options, do: options.tangram_url, else: "https://app.tangram.xyz" %Model{ model: model, log_queue: [], tangram_url: tangram_url } end @doc """ """ def load_model_from_file(path, options \\ nil) do data = File.read!(path) load_model(data, options) end @doc """ """ def predict(model, input, options \\ nil) do _predict(model.model, input, options) end @doc """ """ def log_prediction(model, args) do event = prediction_event(model, args) log_events(model.tangram_url, event) end @doc """ """ def log_true_value(model, args) do event = true_value_event(model, args) log_events(model.tangram_url, event) end @doc """ """ def enqueue_log_prediction(model, args) do event = prediction_event(model, args) %{model | log_queue: model.log_queue ++ [event]} end @doc """ """ def enqueue_log_true_value(model, args) do event = true_value_event(model, args) %{model | log_queue: model.log_queue ++ [event]} end @doc """ """ def flush_log_queue(model) do log_events(model.tangram_url, model.log_queue) %{model | log_queue: []} end def log_events(tangram_url, events) do url = tangram_url <> "/track" headers = %{"Content-Type": "application/json"} body = Jason.encode!(events) HTTPoison.post!(url, body, headers) end def prediction_event(model, args) do model_id = _model_id(model.model) %LogPredictionEvent{ type: "prediction", model_id: model_id, date: DateTime.utc_now() |> DateTime.to_iso8601(), identifier: args.identifier, options: args.options, input: args.input, output: args.output } end def true_value_event(model, args) do model_id = _model_id(model.model) %LogTrueValueEvent{ type: "true_value", model_id: model_id, date: DateTime.utc_now() |> DateTime.to_iso8601(), identifier: args.identifier, true_value: args.true_value } end def _load_model(_) do :erlang.nif_error(:nif_not_loaded) end def _model_id(_) do :erlang.nif_error(:nif_not_loaded) end def _predict(_, _, _) do :erlang.nif_error(:nif_not_loaded) end end