Documentation
¶
Overview ¶
Package jevlar is a typed, correct-by-construction client for Jev, the decision API served at api.typesafe.ai.
Jev answers three kinds of question about a piece of content. A noul question returns the probability that a yes/no statement holds. A choice question selects one of several labelled alternatives and reports the probability of each. A score question rates the content against an ordered rubric and returns a probability-weighted expected level.
The package encodes each of those shapes in the type system so that the compiler, rather than a runtime check, rules out the common mistakes:
- A Question[A] carries the only decoder able to produce its answer, so a noul question cannot be read as a score and a choice cannot yield a label that was never requested. A is constrained to the closed Answer set, so nothing else can pose as a question's result.
- A choice question maps each label to a caller-owned Go value. The selected alternative therefore arrives as the caller's own type, ready for an exhaustive switch.
- A Batch[A] combines independent named questions into a single typed answer, either a struct assembled with Map2 through Map4 or a slice assembled with All. Answer names cannot drift from the questions that produced them because both live in the same value. Batch.Map and Client.Evaluate are generic methods, so the answer type flows through ordinary method calls; this is why the module requires Go 1.27.
- Content is an interface implemented only by Text, Object, and Array, so a bare number, boolean, or null can never be sent where the API rejects it.
- Probability is an opaque value in the closed interval [0, 1]. The library never turns a probability into a boolean on the caller's behalf.
The remaining constraints that cannot be expressed as types, such as the permitted number of alternatives or rubric levels, are checked once when the question is built, before any network traffic.
A Client sends prepared requests over HTTP. The pure request layer, EvaluationRequest and ModelsRequest together with Request.Decode, is exported for callers that own their own transport.
Example ¶
Example shows a mixed batch evaluated end to end. The fixture server stands in for api.typesafe.ai so the example runs offline; drop the WithBaseURL option and supply a real API key to talk to the service.
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/roasbeef/jevlar"
)
// Queue is the application's own routing decision. Jev only ever sees the
// labels; the library hands back one of these values.
type Queue int
const (
Billing Queue = iota
Support
Sales
)
// Triage is what the application wants to know about an inbound message.
type Triage struct {
Route jevlar.ChoiceAnswer[Queue]
Urgency jevlar.ScoreAnswer
Spam jevlar.Probability
}
// Example shows a mixed batch evaluated end to end. The fixture server stands
// in for api.typesafe.ai so the example runs offline; drop the WithBaseURL
// option and supply a real API key to talk to the service.
func main() {
srv := httptest.NewServer(http.HandlerFunc(fixtureHandler))
defer srv.Close()
client := jevlar.NewClient("your-api-key", jevlar.WithBaseURL(srv.URL))
// Building a choice binds each label to a Go value. The count and
// label uniqueness are checked here, before any request exists.
route, err := jevlar.Choice(
jevlar.Text("Which team should handle this?"),
jevlar.Alt("billing", Billing).Describe(
jevlar.Text("Charges, refunds, and invoices"),
),
jevlar.Alt("support", Support),
jevlar.Alt("sales", Sales),
)
if err != nil {
panic(err)
}
urgency, err := jevlar.Score(jevlar.Text("How urgent is this?"),
jevlar.Text("Can wait"),
jevlar.Text("Needs attention this week"),
jevlar.Text("Needs attention today"),
)
if err != nil {
panic(err)
}
// Three independent questions become one typed answer. The names are
// the keys Jev uses in its reply; they never leak past this call.
batch := jevlar.Map3(
jevlar.Named("route", route),
jevlar.Named("urgency", urgency),
jevlar.Named("spam", jevlar.Noul(jevlar.Text("Is this spam?"))),
func(r jevlar.ChoiceAnswer[Queue], u jevlar.ScoreAnswer,
s jevlar.Probability) Triage {
return Triage{Route: r, Urgency: u, Spam: s}
},
)
eval, err := client.Evaluate(context.Background(),
jevlar.Text("I was charged twice for last month. Please help."),
batch,
)
if err != nil {
panic(err)
}
// The switch is over the application's type, so the compiler can see
// every case.
switch eval.Answers.Route.Selected {
case Billing:
fmt.Println("route: billing")
case Support:
fmt.Println("route: support")
case Sales:
fmt.Println("route: sales")
}
fmt.Println("urgency level:", eval.Answers.Urgency.Nearest())
fmt.Println("spam:", eval.Answers.Spam)
fmt.Println("model:", eval.Model)
}
// fixtureHandler replies with a canned answer shaped like the real service.
func fixtureHandler(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `{
"model": "jev-1.13.0",
"answers": {
"route": {
"type": "choice", "choice": "billing",
"confidence": 0.9,
"probabilities": {
"billing": 0.9, "support": 0.08,
"sales": 0.02
}
},
"urgency": {
"type": "score", "score": 1.8,
"confidence": 0.8,
"legend": {
"0": "Can wait",
"1": "Needs attention this week",
"2": "Needs attention today"
},
"probabilities": {
"0": 0.05, "1": 0.1, "2": 0.85
}
},
"spam": {"type": "noul", "noul": 0.02}
},
"usage": {"input_tokens": 120, "output_tokens": 12}
}`)
}
Output: route: billing urgency level: 2 spam: 0.02 model: jev-1.13.0
Index ¶
- Constants
- Variables
- func IsRetryable(err error) bool
- type Alternative
- type Answer
- type Array
- type Batch
- func All[A any](batches ...Batch[A]) Batch[[]A]
- func Map2[A, B, C any](ba Batch[A], bb Batch[B], f func(A, B) C) Batch[C]
- func Map3[A, B, C, D any](ba Batch[A], bb Batch[B], bc Batch[C], f func(A, B, C) D) Batch[D]
- func Map4[A, B, C, D, E any](ba Batch[A], bb Batch[B], bc Batch[C], bd Batch[D], f func(A, B, C, D) E) Batch[E]
- func Named[A Answer](name string, q Question[A]) Batch[A]
- type ChoiceAnswer
- type Client
- func (c *Client) Do[A any](ctx context.Context, req *Request[A]) (A, error)
- func (c *Client) Evaluate[A any](ctx context.Context, state Content, batch Batch[A]) (*Evaluation[A], error)
- func (c *Client) EvaluateWith[A any](ctx context.Context, model Model, state Content, batch Batch[A]) (*Evaluation[A], error)
- func (c *Client) Model() Model
- func (c *Client) Models(ctx context.Context) ([]ModelInfo, error)
- type Content
- type Evaluation
- type HTTPError
- type Model
- type ModelInfo
- type Object
- type Option
- type Outcome
- type Probability
- type Question
- func Choice[T any](instructions Content, alternatives ...Alternative[T]) (Question[ChoiceAnswer[T]], error)
- func Noul(instructions Content) Question[Probability]
- func NoulWithCriteria(instructions, yes, no Content) Question[Probability]
- func Score(instructions Content, levels ...Content) (Question[ScoreAnswer], error)
- type Request
- type ScoreAnswer
- type Text
- type Usage
Examples ¶
Constants ¶
const ( // DefaultBaseURL is the official API origin. DefaultBaseURL = "https://api.typesafe.ai" // MaxResponseBytes bounds how much of a response body the Client will // read. A well-formed answer is a few kilobytes at most. MaxResponseBytes = 4 << 20 )
const ( // MaxAlternatives is the largest number of alternatives a choice // question may offer. MaxAlternatives = 255 // MinScoreLevels is the smallest usable rubric. A single level cannot // discriminate anything. MinScoreLevels = 2 // MaxScoreLevels is the largest rubric the API documents. MaxScoreLevels = 10 )
Variables ¶
var ( // ErrNilState is returned when an evaluation is prepared without any // content to evaluate. ErrNilState = errors.New("jevlar: state content is required") // ErrEmptyModel is returned when the model name is empty or blank. ErrEmptyModel = errors.New("jevlar: model name is empty") // ErrEmptyBatch is returned when a batch holds no questions. The API // requires at least one. ErrEmptyBatch = errors.New("jevlar: batch has no questions") // ErrEmptyName is returned when a question is named with the empty // string. ErrEmptyName = errors.New("jevlar: question name is empty") // ErrDuplicateName is returned when two questions in a batch share a // name. A JSON object cannot carry both. ErrDuplicateName = errors.New("jevlar: duplicate question name") // ErrChoiceCount is returned when a choice question has fewer than one // or more than MaxAlternatives alternatives. ErrChoiceCount = errors.New( "jevlar: choice requires between 1 and 255 alternatives", ) // ErrEmptyLabel is returned when a choice alternative has an empty // label. ErrEmptyLabel = errors.New("jevlar: choice label is empty") // ErrDuplicateLabel is returned when two alternatives in a choice share // a label. ErrDuplicateLabel = errors.New("jevlar: duplicate choice label") // ErrScoreCount is returned when a score question has fewer than // MinScoreLevels or more than MaxScoreLevels rubric levels. ErrScoreCount = errors.New( "jevlar: score requires between 2 and 10 levels", ) // ErrProbabilityRange is returned when a number outside [0, 1] is used // where a probability is required. ErrProbabilityRange = errors.New( "jevlar: probability must be within [0, 1]", ) // ErrInvalidContent is returned when Marshal is given a value whose // JSON form is not a string, object, or array. ErrInvalidContent = errors.New( "jevlar: content must encode to a string, object, or array", ) // ErrInvalidResponse wraps every failure to decode a 200 response into // the answer the request contracted for. ErrInvalidResponse = errors.New("jevlar: invalid response") // ErrMissingAPIKey is returned by the Client when no API key was // supplied. ErrMissingAPIKey = errors.New("jevlar: API key is required") // ErrResponseTooLarge is returned by the Client when a response body // exceeds MaxResponseBytes. ErrResponseTooLarge = errors.New("jevlar: response body too large") )
Functions ¶
func IsRetryable ¶
IsRetryable reports whether err is an *HTTPError whose status the service suggests retrying.
Types ¶
type Alternative ¶
Alternative is one labelled option of a choice question. Label is the string Jev evaluates and returns. Value is the caller's own representation of that outcome, which the decoded answer carries in place of the label. Description is optional; nil tells Jev to interpret the label alone.
func Alt ¶
func Alt[T any](label string, value T) Alternative[T]
Alt is a shorthand constructor for an undescribed Alternative.
func (Alternative[T]) Describe ¶
func (a Alternative[T]) Describe(description Content) Alternative[T]
Describe returns a copy of the alternative with a description attached.
type Answer ¶
type Answer interface {
// contains filtered or unexported methods
}
Answer is the closed set of answer types Jev can return: a Probability for a noul, a ChoiceAnswer for a choice, and a ScoreAnswer for a score. It constrains Question so that no other type can pose as an answer.
type Batch ¶
type Batch[A any] struct { // contains filtered or unexported fields }
Batch is a set of independently named questions whose answers combine into a single value of type A. Every question in a batch is answered by one request, and A is fixed when the batch is built, so the shape of the answer is known before any I/O happens.
Start with Named to give one question a name, then combine batches with Map2, Map3, or Map4 to build a struct, or with All to build a slice. The Map method transforms an answer without changing the questions.
Questions in a batch are independent: no question can observe another's answer. A decision that depends on an earlier answer needs a second request.
func All ¶
All combines any number of batches with the same answer type into one whose answer is a slice in input order. An empty input yields an empty batch, which EvaluationRequest rejects with ErrEmptyBatch.
func Map4 ¶
func Map4[A, B, C, D, E any](ba Batch[A], bb Batch[B], bc Batch[C], bd Batch[D], f func(A, B, C, D) E) Batch[E]
Map4 combines four batches into one whose answer is f applied to all.
type ChoiceAnswer ¶
type ChoiceAnswer[T any] struct { Selected T Label string Confidence Probability Probabilities []Outcome[T] }
ChoiceAnswer is the decoded answer to a choice question. Selected is the value of the alternative the service selected, already converted to the caller's type. The library does not re-derive the winner from Probabilities. Label is the wire label of that alternative, kept for logging. Probabilities lists every requested alternative in request order.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client sends prepared requests to Jev over HTTP. It performs exactly one attempt per call, adds the bearer credential, and bounds the response size. Retries, if wanted, are the caller's policy; HTTPError.Retryable and HTTPError.RetryAfter expose the service's guidance.
A Client is safe for concurrent use.
func NewClient ¶
NewClient creates a client that authenticates with apiKey. The key is only ever written to the Authorization header of outgoing requests.
func (*Client) Do ¶
Do executes a prepared request and decodes its response. A non-200 status is returned as an *HTTPError carrying the status, headers, and raw body. A 200 body that violates the request's contract is returned as an error wrapping ErrInvalidResponse.
func (*Client) Evaluate ¶
func (c *Client) Evaluate[A any](ctx context.Context, state Content, batch Batch[A]) (*Evaluation[A], error)
Evaluate asks every question in the batch about state using the client's default model. The returned Evaluation carries the batch's answer type.
func (*Client) EvaluateWith ¶
func (c *Client) EvaluateWith[A any](ctx context.Context, model Model, state Content, batch Batch[A]) (*Evaluation[A], error)
EvaluateWith is Evaluate with an explicit model, for pinning a version on a single call.
type Content ¶
type Content interface {
// contains filtered or unexported methods
}
Content is a JSON value that Jev accepts as state, instructions, a choice description, or a rubric level. Only Text, Object, and Array implement it. The API rejects a top-level number, boolean, or null, so those shapes cannot be expressed at all. Nested values inside an Object or Array are ordinary JSON and may hold any type.
type Evaluation ¶
type Evaluation[A any] struct { // Model is the model that answered, which may differ from the alias // that was requested. Model Model // Answers is the combined answer of the batch. Answers A // Usage is the provider-reported token accounting. Usage Usage }
Evaluation is the decoded result of one evaluation request. Answers has the batch's statically known type.
type HTTPError ¶
HTTPError is a non-200 response from the service. The body is kept verbatim because 422 responses carry a JSON list of validation problems, while proxy errors may be HTML or plain text. The body can echo request content, so callers decide whether to log it.
func (*HTTPError) Error ¶
Error summarises the failure with the status and a bounded excerpt of the body.
func (*HTTPError) RetryAfter ¶
RetryAfter returns the delay the service asked for, read from the retry-after-ms header or a Retry-After header holding either a delay in seconds or an HTTP date. The second result is false when neither header is usable.
type Model ¶
type Model string
Model names a Jev model or alias accepted by the evaluation endpoint.
const Latest Model = "jev-latest"
Latest is the provider's moving alias for the current stable model. Pin a versioned name for evaluations that must stay reproducible.
type ModelInfo ¶
type ModelInfo struct {
// Name is accepted as the Model of an evaluation.
Name Model `json:"name"`
// Description is the provider's summary of the model.
Description string `json:"description"`
// ReleaseDate is the provider's date string, usually YYYY-MM-DD.
ReleaseDate string `json:"release_date"`
}
ModelInfo describes one model returned by the model catalogue.
type Object ¶
Object is structured content with named fields. Keys are emitted in sorted order by encoding/json.
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithBaseURL ¶
WithBaseURL points the client at a different origin, such as a test server or a proxy. A trailing slash is removed.
func WithHTTPClient ¶
WithHTTPClient replaces the underlying HTTP client, for custom timeouts, proxies, or transports.
type Outcome ¶
type Outcome[T any] struct { Value T Probability Probability }
Outcome pairs one alternative's value with the probability Jev assigned to it.
type Probability ¶
type Probability struct {
// contains filtered or unexported fields
}
Probability is a number in the closed interval [0, 1]. It is opaque so that a value outside that range cannot be constructed. Jev reports probabilities and confidences with this type; converting one into a yes/no decision is left to the caller, who knows the cost of each kind of mistake.
func NewProbability ¶
func NewProbability(v float64) (Probability, error)
NewProbability validates v and wraps it. It fails with ErrProbabilityRange for NaN and for any value outside [0, 1].
func (Probability) Float64 ¶
func (p Probability) Float64() float64
Float64 returns the underlying value for ranking, thresholding, and arithmetic.
func (Probability) String ¶
func (p Probability) String() string
String formats the probability with the shortest representation that round trips.
type Question ¶
type Question[A Answer] struct { // contains filtered or unexported fields }
Question is a question about some content together with the only decoder able to read its answer. The type parameter A is the answer type, so the compiler tracks which kind of answer each question produces. Build one with Noul, NoulWithCriteria, Choice, or Score, then name it with Named to place it in a Batch.
func Choice ¶
func Choice[T any](instructions Content, alternatives ...Alternative[T]) (Question[ChoiceAnswer[T]], error)
Choice builds a question that selects one of the supplied alternatives. The answer's Selected field has the alternatives' value type T. It fails with ErrChoiceCount, ErrEmptyLabel, or ErrDuplicateLabel when the alternatives cannot be expressed as a JSON object of distinct labels.
func Noul ¶
func Noul(instructions Content) Question[Probability]
Noul builds a yes/no question. The answer is the probability that the statement in instructions holds for the content. A nil instructions value omits the field, which the API permits when the question is clear from context.
func NoulWithCriteria ¶
func NoulWithCriteria(instructions, yes, no Content) Question[Probability]
NoulWithCriteria builds a yes/no question with optional evidence for each side. Either criterion may be nil, in which case it is omitted.
func Score ¶
func Score(instructions Content, levels ...Content) (Question[ScoreAnswer], error)
Score builds a question that rates the content against an ordered rubric. Levels are described from lowest to highest; the level's position is its score, starting at zero. It fails with ErrScoreCount when the rubric is outside [MinScoreLevels, MaxScoreLevels].
type Request ¶
type Request[A any] struct { // contains filtered or unexported fields }
Request is a prepared HTTP request together with the only decoder able to read its 200 response. The Client executes it; callers with their own transport can read Method, Path, and Body, perform the exchange, and hand the 200 body to Decode.
func EvaluationRequest ¶
func EvaluationRequest[A any](model Model, state Content, batch Batch[A]) (*Request[Evaluation[A]], error)
EvaluationRequest prepares a POST to /v1/systemone that asks every question in the batch about state. It reports ErrNilState, ErrEmptyModel, ErrEmptyBatch, ErrEmptyName, or ErrDuplicateName before building a body, and any Content that fails to encode.
func ModelsRequest ¶
ModelsRequest prepares a GET of /v1/models, which lists the models and aliases available to the authenticated account.
type ScoreAnswer ¶
type ScoreAnswer struct {
Value float64
Confidence Probability
Legend []Content
Probabilities []Probability
}
ScoreAnswer is the decoded answer to a score question. Value is the probability-weighted expected level and may fall between two integer levels. Legend and Probabilities are indexed by level, in the order the rubric was supplied, and always have exactly one entry per level.
func (ScoreAnswer) Nearest ¶
func (s ScoreAnswer) Nearest() int
Nearest returns the integer level closest to Value. Ties round up.