SystemOneClient behaviour (SystemOneClient v0.3.0)

Copy Markdown View Source

Client for System One decision models: TypeSafe Jev, Cloudflare Clef, OpenAI's Decisions API, OpenRouter's decisions endpoint, and any self-hosted /v1/systemone server such as CLM.

Ask typed questions (noul, choice, score) about arbitrary JSON state and get typed answers back with their full probability distributions. SystemOneClient.HTTP talks to a provider (see SystemOneClient.Provider); SystemOneClient.Stub returns canned answers for tests. Question maps follow the API wire shape verbatim; this library does not invent its own schema for them.

{:ok, answers, meta} = SystemOneClient.evaluate(state, questions, provider: :cloudflare)
answers["tool"].probabilities

Summary

Types

Errors returned by SystemOneClient.HTTP.

Options accepted by evaluate/3. See the README for defaults.

A question in the System One wire format, for example %{"type" => "choice", "instructions" => "...", "criteria" => %{"a" => "...", "b" => "..."}}. Keys may be strings or atoms.

A question id. Answers are keyed by its string form.

Functions

Evaluates questions with the configured client (opts[:client], else app env :client, else HTTP).

Builds a base64 data URL for the images: option from image bytes.

Checks every question has a known type. Ids may be atoms or strings.

Types

answers()

@type answers() :: %{required(String.t()) => SystemOneClient.Answer.t()}

error()

@type error() ::
  :missing_api_key
  | :missing_account_id
  | :missing_url
  | {:unknown_provider, term()}
  | {:invalid_question, String.t(),
     :unknown_type | :not_a_map | :missing_criteria}
  | {:http, non_neg_integer(), term()}
  | {:transport, term()}
  | {:malformed_answers, term()}
  | {:malformed_envelope, term()}
  | {:provider_error, list()}
  | SystemOneClient.Images.error()

Errors returned by SystemOneClient.HTTP.

meta()

@type meta() :: %{
  optional(:provider) => SystemOneClient.Provider.name(),
  model: String.t() | nil,
  usage: map() | nil,
  latency_ms: non_neg_integer()
}

option()

@type option() ::
  {:client, module()}
  | {:provider, SystemOneClient.Provider.name()}
  | {:api_key, String.t() | nil}
  | {:account_id, String.t()}
  | {:url, String.t()}
  | {:model, String.t()}
  | {:receive_timeout, timeout()}
  | {:max_retries, non_neg_integer()}
  | {:retry_delay_ms, non_neg_integer()}
  | {:plug, term()}
  | {:env, %{optional(String.t()) => String.t()}}
  | {:answers, map() | (term(), questions() -> map() | {:error, term()})}
  | {:images, [String.t()]}

Options accepted by evaluate/3. See the README for defaults.

options()

@type options() :: [option()]

question()

@type question() :: map()

A question in the System One wire format, for example %{"type" => "choice", "instructions" => "...", "criteria" => %{"a" => "...", "b" => "..."}}. Keys may be strings or atoms.

question_id()

@type question_id() :: String.t() | atom()

A question id. Answers are keyed by its string form.

questions()

@type questions() :: %{required(question_id()) => question()}

Callbacks

evaluate(state, questions, opts)

@callback evaluate(state :: term(), questions :: questions(), opts :: options()) ::
  {:ok, answers(), meta()} | {:error, term()}

Functions

evaluate(state, questions, opts \\ [])

@spec evaluate(term(), questions(), options()) ::
  {:ok, answers(), meta()} | {:error, term()}

Evaluates questions with the configured client (opts[:client], else app env :client, else HTTP).

image_data_url(bytes, media_type)

@spec image_data_url(binary(), String.t()) :: String.t()

Builds a base64 data URL for the images: option from image bytes.

iex> SystemOneClient.image_data_url("abc", "image/png")
"data:image/png;base64,YWJj"

Raises ArgumentError if media_type is not an image/* type.

validate_questions(questions)

@spec validate_questions(map()) ::
  :ok | {:error, {:invalid_question, String.t(), :unknown_type | :not_a_map}}

Checks every question has a known type. Ids may be atoms or strings.

iex> SystemOneClient.validate_questions(%{"q" => %{"type" => "noul", "instructions" => "?"}})
:ok

iex> SystemOneClient.validate_questions(%{"q" => %{"type" => "guess"}})
{:error, {:invalid_question, "q", :unknown_type}}