An Elixir client for TypeSafe's System One API.
System One models, such as Jev, don't generate text. You send some state and a map of typed questions, and get back one typed answer per question, with probabilities your code can act on:
| Question | Answers | Answer |
|---|---|---|
noul/2 | whether a condition holds | TypeSafe.NoulAnswer - probability of yes |
choice/2 | which one of a defined set | TypeSafe.ChoiceAnswer - option, probabilities, confidence |
score/2 | how far along described levels | TypeSafe.ScoreAnswer - weighted level, probabilities, confidence |
client = TypeSafe.new()
{:ok, response} =
TypeSafe.system_one(client, %{ticket: "I was charged twice. Please fix this ASAP."}, %{
billing: TypeSafe.noul("Is this ticket about billing?"),
tone: TypeSafe.choice("What is the customer's tone?", [:calm, :frustrated, :angry]),
urgency: TypeSafe.score("How urgent is this ticket?", ["Can wait", "This week", "Today"])
})
response.answers.billing.noul #=> 0.97
response.answers.tone.choice #=> :frustrated
response.answers.urgency.score #=> 1.8Questions in one call share one read of the state and are answered in parallel, so ask independent questions together. Each answers without seeing the others.
Configuration
new/1 reads these environment variables when the option is not given:
| Option | Environment variable | Default |
|---|---|---|
:api_key | TYPESAFE_API_KEY | none - calls return :no_api_key |
:base_url | TYPESAFE_BASE_URL | https://api.typesafe.ai |
:model | TYPESAFE_DEFAULT_MODEL | jev-latest |
It also takes these options:
:timeout- milliseconds to wait for each response (default 10_000):retry- seeTypeSafe.Retry:headers- extra request headers:req_options- merged into theReqrequest last, for example[plug: {Req.Test, MyApp.TypeSafe}]in tests
Summary
Types
Anything that encodes to JSON: strings, numbers, booleans, nil, lists and maps.
Functions
A question that picks one option. Options are atoms or strings, in order, with an optional description each
Whether the client has an API key.
The model names the account can send, aliases included. Versioned ids such as
"jev-1.13.0" are accepted whether or not they are listed.
Like models/1, but returns the list or raises TypeSafe.Error.
Builds a client. See the module documentation for the options.
A yes/no question. criteria optionally says what yes and no mean
A question that rates the state against ordered levels, at least two, worst or least first. Each level should describe a concrete situation on its own
Asks questions about state in one request.
Like system_one/4, but returns the response or raises TypeSafe.Error.
Types
@type json() :: String.t() | number() | boolean() | nil | [json()] | %{optional(atom() | String.t()) => json()}
Anything that encodes to JSON: strings, numbers, booleans, nil, lists and maps.
@type questions() :: %{ required(atom() | String.t()) => TypeSafe.Noul.t() | TypeSafe.Choice.t() | TypeSafe.Score.t() | map() }
Functions
@spec choice(json(), keyword() | map() | [TypeSafe.Choice.option()]) :: TypeSafe.Choice.t()
A question that picks one option. Options are atoms or strings, in order, with an optional description each:
TypeSafe.choice("Which team should own this ticket?",
billing: "Charges, invoices, refunds",
technical: "Bugs, outages, integrations",
unclear: "The ticket does not say enough to tell"
)
TypeSafe.choice("What is the tone?", [:calm, :frustrated, :angry])The answer's choice and probabilities use the same keys. Include a no-match
option when nothing may fit, because the model must pick one of those given.
@spec configured?(TypeSafe.Client.t()) :: boolean()
Whether the client has an API key.
@spec models(TypeSafe.Client.t()) :: {:ok, [TypeSafe.Model.t()]} | {:error, TypeSafe.Error.t()}
The model names the account can send, aliases included. Versioned ids such as
"jev-1.13.0" are accepted whether or not they are listed.
@spec models!(TypeSafe.Client.t()) :: [TypeSafe.Model.t()]
Like models/1, but returns the list or raises TypeSafe.Error.
@spec new(keyword()) :: TypeSafe.Client.t()
Builds a client. See the module documentation for the options.
A missing API key is not an error here, so an app can boot without one. Calls
return {:error, %TypeSafe.Error{reason: :no_api_key}} until one is set.
@spec noul(json(), keyword() | map()) :: TypeSafe.Noul.t()
A yes/no question. criteria optionally says what yes and no mean:
TypeSafe.noul("If the user wants to change the lights, do they want them on?",
true: "Lights on or brighter",
false: "Lights off or dimmer"
)instructions can be a string, or a map or list when structure makes the
question clearer.
@spec score(json(), [json()]) :: TypeSafe.Score.t()
A question that rates the state against ordered levels, at least two, worst or least first. Each level should describe a concrete situation on its own:
TypeSafe.score("How urgently does this ticket need a response?", [
"Can wait: a question or feedback, nothing is blocked",
"Soon: a problem with a workaround",
"Today: something important is broken for the customer"
])
@spec system_one(TypeSafe.Client.t(), json(), questions(), keyword()) :: {:ok, TypeSafe.Response.t()} | {:error, TypeSafe.Error.t()}
Asks questions about state in one request.
state is a string, or a map or list for structured context. Questions can
refer to its parts by backticked path, such as `ticket.messages[0]`.
Question ids can be atoms or strings. They name the answers and are never sent to the model, so the question itself has to carry its meaning.
Options:
:model- overrides the client's model for this call:timeout,:retry,:headers- override the client's for this call:extra_body- extra top-level request fields, for API features this client predates
A question map may also hold raw maps (%{"type" => ..., ...}), sent as they are.
Their answers come back as raw maps.
@spec system_one!(TypeSafe.Client.t(), json(), questions(), keyword()) :: TypeSafe.Response.t()
Like system_one/4, but returns the response or raises TypeSafe.Error.