jev_api
A sans-io client for TypeSafe AI’s System One API and its Jev model.
Jev is a decision model: it evaluates one piece of state against typed
questions and returns probabilities, choices and scores instead of
generated text. See jev_api/question for the question types.
“Sans-io” means this library never performs network I/O itself.
evaluate_request builds a gleam/http/request.Request(String) that you
send with whichever HTTP client fits your target (gleam_httpc on Erlang,
gleam_fetch on JavaScript, or anything else), and evaluate_response
decodes the gleam/http/response.Response(String) that comes back.
import gleam/httpc
import gleam/json
import jev_api
import jev_api/question
let client = jev_api.new(api_key)
let req =
jev_api.evaluate_request(
client,
state: json.string("My card was charged twice. Fix it now."),
questions: [
#("urgent", question.noul("Does this message express urgency?")),
#("team", question.choice("Which team should handle this?", ["billing", "technical", "other"])),
],
)
let assert Ok(res) = httpc.send(req)
let assert Ok(evaluation) = jev_api.evaluate_response(res)
Types
The answer to one question, matching the question’s type.
pub type Answer {
NoulAnswer(probability: Float)
ChoiceAnswer(
choice: String,
probabilities: dict.Dict(String, Float),
confidence: Float,
)
ScoreAnswer(
score: Float,
levels: List(Level),
confidence: Float,
)
}
Constructors
-
NoulAnswer(probability: Float)The probability, from 0.0 to 1.0, that the answer to a
Noulquestion is “yes”. Values near 0.5 mean Jev is unsure. -
ChoiceAnswer( choice: String, probabilities: dict.Dict(String, Float), confidence: Float, )The option key Jev picked for a
Choicequestion, the probability of every option, and how peaked that distribution is. -
ScoreAnswer(score: Float, levels: List(Level), confidence: Float)Where on the scale of a
Scorequestion the state falls.scoreis a position on the scale and may sit between two levels;levelscarries the probability of each level in order.
What every request needs: the API key, the model to ask, and the base URL.
pub opaque type Client
Why a response could not be turned into a result.
pub type Error {
AuthenticationError(message: String)
ValidationError(problems: List(ValidationProblem))
RateLimited(message: String, retry_after: option.Option(Int))
Overloaded(message: String)
ApiError(status: Int, error_type: String, message: String)
UnexpectedResponse(status: Int, body: String)
MalformedResponse(
status: Int,
body: String,
error: json.DecodeError,
)
}
Constructors
-
AuthenticationError(message: String)The API key is missing, invalid or not allowed to do this (HTTP 401 or 403, or the API’s
authentication_error). -
ValidationError(problems: List(ValidationProblem))The request body failed validation (HTTP 422): a
nullstate, no questions, an unknown question type, and so on. -
RateLimited(message: String, retry_after: option.Option(Int))Too many requests (HTTP 429). Back off, honouring
retry_afterseconds when the API sends aretry-afterheader. -
Overloaded(message: String)The service is temporarily overloaded (HTTP 529). Retry after a delay.
-
ApiError(status: Int, error_type: String, message: String)Any other error the API described with its
error_type/messageenvelope, for exampleapi_usage_errorfor an unknown model. -
UnexpectedResponse(status: Int, body: String)A non-success status with a body this library does not recognise, such as a 404 or an HTML page from a proxy.
-
MalformedResponse( status: Int, body: String, error: json.DecodeError, )A success status whose body did not decode as expected.
Everything Jev said about one request.
pub type Evaluation {
Evaluation(
model: String,
answers: dict.Dict(String, Answer),
usage: Usage,
request_id: option.Option(String),
)
}
Constructors
-
Evaluation( model: String, answers: dict.Dict(String, Answer), usage: Usage, request_id: option.Option(String), )Arguments
- model
-
The concrete model that answered, e.g.
"jev-1.13.0"for thejev-latestalias. - answers
-
One answer per question, under the key the question was asked with.
- request_id
-
TypeSafe’s id for the request, from the
x-typesafe-request-idheader.
One level of a Score question’s scale as reported in an answer.
pub type Level {
Level(index: Int, label: dynamic.Dynamic, probability: Float)
}
Constructors
-
Level(index: Int, label: dynamic.Dynamic, probability: Float)Arguments
- index
-
The level’s position on the scale, starting at 0.
- label
-
The level as the API echoed it back from the question’s criteria: a string for a string level, or the object you sent for a structured one. Decode it with
gleam/dynamic/decodeif you need it.
A model the account can ask for by name.
pub type ModelCard {
ModelCard(
name: String,
description: String,
release_date: option.Option(String),
)
}
Constructors
-
ModelCard( name: String, description: String, release_date: option.Option(String), )
Token accounting for a request. Only input tokens are billed.
pub type Usage {
Usage(input_tokens: Int, output_tokens: Int)
}
Constructors
-
Usage(input_tokens: Int, output_tokens: Int)
One thing wrong with a request body, as reported in a ValidationError.
pub type ValidationProblem {
ValidationProblem(
location: List(String),
message: String,
kind: String,
)
}
Constructors
-
ValidationProblem( location: List(String), message: String, kind: String, )Arguments
- location
-
Path to the offending field, e.g.
["body", "questions", "urgent"]. - message
-
Human-readable description.
- kind
-
The validator’s tag for the problem, e.g.
"missing".
Values
pub fn answer(
evaluation: Evaluation,
key: String,
) -> Result(Answer, Nil)
Looks up the answer to the question asked under key.
pub const default_base_url: String
Where the hosted API lives. Override it with with_base_url to go through
a proxy or a mock server.
pub const default_model: String
The model alias every request uses unless with_model says otherwise.
pub fn evaluate_request(
client: Client,
state state: json.Json,
questions questions: List(#(String, question.Question)),
) -> request.Request(String)
Builds the request that evaluates questions against state. Send it
with any HTTP client and hand the response to evaluate_response.
state is what Jev reasons about: a string for a single piece of text,
an object so each part of the context has a name (recommended), or an
array of messages or records. The API rejects null and an empty set of
questions with a ValidationError.
pub fn evaluate_response(
response res: response.Response(String),
) -> Result(Evaluation, Error)
Decodes the response to an evaluate_request.
pub fn models_request(client: Client) -> request.Request(String)
Builds the request that lists the models available to the account.
pub fn models_response(
response res: response.Response(String),
) -> Result(List(ModelCard), Error)
Decodes the response to a models_request.
pub fn request_id(
response res: response.Response(body),
) -> option.Option(String)
TypeSafe’s id for the request that produced a response, if present.
Available for error responses too, unlike Evaluation.request_id.
pub const request_id_header: String
The response header that carries TypeSafe’s id for a request. Quote it when reporting a problem to TypeSafe.
pub fn with_base_url(
client: Client,
url: String,
) -> Result(Client, Nil)
Sends requests somewhere other than default_base_url. The URL needs a
scheme and a host; a path prefix is kept (https://proxy.internal/typesafe
yields .../typesafe/v1/systemone). Fails if the URL cannot be parsed.