An Elixir client for the TypeSafe AI System One API.
Designed for latency-sensitive applications. Evaluate shared state with Choice, Score, and Noul questions, and receive validated, typed answers.
Start a TypeSafe.Client under your application's supervisor, then call
system_one/2. Supervised connections, bounded concurrency and queues, request
deadlines, configurable retries, and telemetry give you explicit control over
how your application handles load. You receive TypeSafe.Response and typed
answer structs. The client handles authentication and uses Mint directly.
Elixir 1.18+ and Erlang/OTP 27+ are required; encoding uses native JSON.
Example
With a client registered as MyApp.TypeSafe:
TypeSafe.system_one(MyApp.TypeSafe,
state: %{message: "Please refund the duplicate charge."},
questions: %{
"team" => TypeSafe.choice("Which team?", %{"billing" => nil, "technical" => nil}),
"refund" => TypeSafe.noul("Is a refund requested?")
}
)Batch independent questions about the same state in one call. For separate
states, use bounded tasks sharing a supervised client. See the
getting started guide for complete setup, and
TypeSafe.Client for capacity and lifecycle behavior.
Summary
Evaluation
Evaluates state and returns {:ok, response} or {:error, error}.
Question helpers
Builds a Choice question with 1–255 named options.
Builds a Noul question, whose answer estimates the probability of yes.
Builds a Score question with 2–10 ordered descriptive levels.
Evaluation
@spec system_one(GenServer.server(), keyword()) :: {:ok, TypeSafe.Response.t()} | {:error, TypeSafe.Error.t()}
Evaluates state and returns {:ok, response} or {:error, error}.
client is a PID or registered name on the calling node. Requests use local
monotonic deadlines; remote-node client references return :unavailable.
Options
:state(required) — a string, map, or list of JSON-compatible values.:questions(required) — a non-empty map from non-empty string IDs toTypeSafe.Questionstructs built with the question helpers.:model— a non-empty model identifier overriding the client default.:timeout— a positive overall deadline in milliseconds, overriding the client default. It starts after input encoding and includes queueing, connecting, uploads, response collection, and retry waits.:retry— aTypeSafe.Retrystruct or keyword policy. An override replaces the client policy; omitted fields use the retry defaults. Setretry: [max_attempts: 1]to disable retries.
Unknown options are rejected. API keys are configured per client, never per
request. Invalid questions or unencodable inputs produce validation errors
before contacting the client. Custom structs require a native JSON.Encoder
implementation or conversion to plain JSON-compatible data.
Successful answers preserve question IDs and option labels as strings. The
response type depends on each question; see TypeSafe.Response. Match
the :kind and :status fields of TypeSafe.Error for failures, rather than
matching message text. An ambiguous transport failure is not retried by default.
Examples
Invalid input is rejected without accessing a client:
iex> {:error, error} = TypeSafe.system_one(:unused_client, state: "Hello", questions: %{})
iex> error.kind
:validation
Question helpers
@spec choice(TypeSafe.Question.entry(), map()) :: TypeSafe.Question.t()
Builds a Choice question with 1–255 named options.
instructions can be a string, JSON-compatible map, or list. criteria is a
map of non-empty string labels to descriptions; a description may be a string,
map, list, or nil when the label needs no explanation.
Construction performs no network I/O or validation. system_one/2 validates
the question before submitting it. Answers use TypeSafe.Answer.Choice.
Examples
iex> question = TypeSafe.choice("Which team?", %{"billing" => "Payments", "technical" => nil})
iex> question.type
:choice
iex> question.criteria["billing"]
"Payments"
@spec noul(TypeSafe.Question.entry(), map() | nil) :: TypeSafe.Question.t()
Builds a Noul question, whose answer estimates the probability of yes.
Optional criteria must contain exactly the string keys "true" and "false".
Omit criteria when the instructions alone explain the decision. The returned
TypeSafe.Answer.Noul contains a value from 0 to 1, without a separate confidence.
The caller chooses any threshold used to turn that probability into a decision.
Examples
iex> question = TypeSafe.noul("Is a refund requested?")
iex> {question.type, question.criteria}
{:noul, nil}
iex> question = TypeSafe.noul("Urgent?", %{"true" => "Work is blocked", "false" => "Can wait"})
iex> question.criteria["true"]
"Work is blocked"
@spec score(TypeSafe.Question.entry(), [TypeSafe.Question.entry()]) :: TypeSafe.Question.t()
Builds a Score question with 2–10 ordered descriptive levels.
Level indices start at zero. A returned score is probability-weighted and may fall between levels; with three levels its range is 0–2, not 0–1 or 0–3. Instructions and level descriptions can contain structured JSON values.
Questions are validated by system_one/2. Answers use TypeSafe.Answer.Score.
Examples
iex> question = TypeSafe.score("How urgent?", ["Routine", "Today", "Immediately"])
iex> question.type
:score
iex> Enum.at(question.criteria, 2)
"Immediately"