Unofficial Elixir client for the TypeSafe AI API. Not affiliated with or endorsed by TypeSafe AI.
TypeSafe's System One API answers typed questions about a piece of state:
a yes/no probability (noul/2), one option from a set (choice/2), or a
position on an ordered scale (score/2). This module is the whole public
surface for everyday use; the modules it delegates to hold the details.
client = TypeSafe.new(api_key: "...")
{:ok, result} =
TypeSafe.evaluate(client, "Help! My payouts have been failing for 3 days.",
urgent: TypeSafe.noul("Does this convey urgency?"),
dept:
TypeSafe.choice("Which team should handle this?",
billing: "Payments, invoicing, refunds",
technical: "Bugs, outages, integrations",
sales: nil
),
anger: TypeSafe.score("How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"])
)
result.answers.dept.choice #=> :technical
result.answers.anger.label #=> "Very angry"
result.usage.input_tokens #=> 312The same example, run against TypeSafe.Test stubs instead of the network
(this block is a doctest):
iex> client =
...> TypeSafe.Test.client()
...> |> TypeSafe.Test.stub(
...> urgent: {:noul, 0.92},
...> dept: {:choice, :technical, 0.82},
...> anger: {:score, 2, 0.65}
...> )
iex> {:ok, result} =
...> TypeSafe.evaluate(client, "Help! My payouts have been failing for 3 days.",
...> urgent:
...> TypeSafe.noul("Does this convey urgency?",
...> true: "Explicitly time-sensitive",
...> false: "No urgency expressed"
...> ),
...> dept:
...> TypeSafe.choice("Which team should handle this?",
...> billing: "Payments, invoicing, refunds",
...> technical: "Bugs, outages, integrations",
...> sales: nil
...> ),
...> anger: TypeSafe.score("How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"])
...> )
iex> result.model
"jev-latest"
iex> result.answers.urgent
%TypeSafe.Answer.Noul{id: :urgent, noul: 0.92}
iex> {result.answers.dept.choice, result.answers.dept.confidence}
{:technical, 0.82}
iex> Map.keys(result.answers.dept.probabilities) |> Enum.sort()
[:billing, :sales, :technical]
iex> {result.answers.anger.level, result.answers.anger.label}
{2, "Very angry"}
iex> Enum.map(result.answers.anger.levels, &elem(&1, 0))
["Calm", "Frustrated", "Very angry"]
iex> TypeSafe.Answer.gate(result.answers.dept, act: 0.8, review: 0.5)
:act
iex> TypeSafe.Answer.gate(result.answers.anger, act: 0.8, review: 0.5)
:review
iex> TypeSafe.Answer.yes?(result.answers.urgent)
trueQuestion ids and Choice option keys come back exactly as you gave them:
atoms stay atoms, strings stay strings. See TypeSafe.Keys for why.
Questions are validated locally on every call, so a malformed one fails with
a :validation error before anything is sent. To catch the mistake at the
line that wrote it instead, wrap the constructor in
TypeSafe.Question.validate!/1, which raises ArgumentError and returns the
question unchanged. That is the eager-validation path for questions built
once in a module attribute or at application start.
Two layers
TypeSafe.HTTP is the raw layer: maps in, maps out, with auth, retries and
telemetry handled. Everything above it (questions, answers, results) is the
typed layer. If the API adds something this library does not model yet, the
raw layer still works.
Summary
Functions
Pick one option from criteria, a keyword list (or list of pairs) of option
key to description; nil when the key speaks for itself. Order is
preserved. See TypeSafe.Question.Choice.
Evaluates state against questions and returns a TypeSafe.Result.
Like evaluate/4 but returns the result or raises the TypeSafe.Error.
Evaluates many states against one question set, concurrently, and returns
one outcome per state in input order. See TypeSafe.FanOut for how errors,
timeouts and exhausted retries surface.
Lists the models available to the account. See TypeSafe.Models.list/2.
Builds a TypeSafe.Client.
A yes/no question. Optional true: and false: descriptions say what each
answer means. See TypeSafe.Question.Noul.
Rate the state along levels, an ordered list of two to ten level
descriptions from low to high. See TypeSafe.Question.Score.
Functions
@spec choice( TypeSafe.Question.description(), [TypeSafe.Question.Choice.option()] | map() ) :: TypeSafe.Question.Choice.t()
Pick one option from criteria, a keyword list (or list of pairs) of option
key to description; nil when the key speaks for itself. Order is
preserved. See TypeSafe.Question.Choice.
@spec evaluate( TypeSafe.Client.t(), TypeSafe.SystemOne.state(), TypeSafe.Question.input(), keyword() ) :: {:ok, TypeSafe.Result.t()} | {:error, TypeSafe.Error.t()}
Evaluates state against questions and returns a TypeSafe.Result.
state is a string or JSON-shaped map or list. questions is a keyword
list or map of id to question.
Options
:model(String.t/0) - Model for this call; defaults to the client's model.:timeout(pos_integer/0) - Timeout in milliseconds for this call.:retry- Retry policy for this call; seeTypeSafe.Retry.new/1.:telemetry(map/0) - Extra metadata merged into telemetry events.
@spec evaluate!( TypeSafe.Client.t(), TypeSafe.SystemOne.state(), TypeSafe.Question.input(), keyword() ) :: TypeSafe.Result.t()
Like evaluate/4 but returns the result or raises the TypeSafe.Error.
@spec evaluate_many( TypeSafe.Client.t(), Enumerable.t(), TypeSafe.Question.input(), keyword() ) :: [TypeSafe.FanOut.outcome()] | {:error, TypeSafe.Error.t()}
Evaluates many states against one question set, concurrently, and returns
one outcome per state in input order. See TypeSafe.FanOut for how errors,
timeouts and exhausted retries surface.
Options
:max_concurrency(pos_integer/0) - Maximum number of in-flight requests. A guess; see the module docs. The default value is8.:timeout- Per-state task timeout in milliseconds, covering every retry of that state. Defaults to the retry budget plus one attempt timeout, or 60 000 when the budget is disabled.:attempt_timeout(pos_integer/0) - Timeout in milliseconds for one HTTP attempt; defaults to the client's.:ordered(boolean/0) - Return results in input order.falseyields them as they finish. The default value istrue.:on_error-:collectreturns error tuples in place;:raiseraises the first error. The default value is:collect.
Every other option is passed through to each individual call, so :model,
:retry, :req_options and :telemetry mean the same here as in
evaluate/4. The one exception is :timeout, which is the per-state task
timeout above; use :attempt_timeout for the single HTTP attempt.
@spec models(TypeSafe.Client.t(), keyword()) :: {:ok, [TypeSafe.Model.t()]} | {:error, TypeSafe.Error.t()}
Lists the models available to the account. See TypeSafe.Models.list/2.
@spec new(keyword()) :: TypeSafe.Client.t()
Builds a TypeSafe.Client.
With no options, configuration comes from application config and the
TYPESAFE_API_KEY, TYPESAFE_BASE_URL, and TYPESAFE_DEFAULT_MODEL
environment variables. See TypeSafe.Client.new/1 for all options.
@spec noul(TypeSafe.Question.description(), keyword()) :: TypeSafe.Question.Noul.t()
A yes/no question. Optional true: and false: descriptions say what each
answer means. See TypeSafe.Question.Noul.
@spec score(TypeSafe.Question.description(), [TypeSafe.Question.Score.level()]) :: TypeSafe.Question.Score.t()
Rate the state along levels, an ordered list of two to ten level
descriptions from low to high. See TypeSafe.Question.Score.