Documentation
¶
Overview ¶
Package typesafe is a client for the TypeSafe System One API.
System One answers questions about content with a typed judgment and a probability instead of generating text you then have to parse. Ask three questions about a support ticket and you get back a number, an option, and a rating. No JSON mode, no retry loop around malformed output.
Getting started ¶
client, err := typesafe.New() // reads TYPESAFE_API_KEY
if err != nil {
return err
}
urgent := typesafe.Noul("is_urgent", "Does this convey urgency?")
res, err := client.Ask(ctx, ticket, urgent)
if err != nil {
return err
}
p, err := urgent.From(res) // 0.92
Question handles ¶
A question is a value you keep. It carries its own id and its own answer type, so you read an answer back through the question that asked it. The id string appears exactly once in your code, and a Score handle cannot read a Noul answer, because From returns a different type for each.
The three primitives ¶
Noul asks yes or no and returns the probability of yes. It has no confidence field: 0.5 means the model is genuinely torn, which is an answer, not a failure.
Choice picks one option from a set you define and returns the whole distribution alongside the winner. Declare the option set as your own string type and the compiler checks every comparison you write against it.
Score rates content against ordered levels and returns a probability-weighted position that can land between them. On a calm/frustrated/angry scale, 1.6 means mostly angry.
Probabilities are the point ¶
Route on the number. A 0.95 and a 0.51 both become "yes" after a threshold, and collapsing them throws away what you paid for.
Configuration ¶
New reads TYPESAFE_API_KEY, TYPESAFE_BASE_URL, and TYPESAFE_DEFAULT_MODEL, and an option overrides any of them. Transient failures retry on their own: two retries, 500ms doubling to 5s, honoring Retry-After, inside a 30-second budget. See RetryPolicy to change that.
Errors ¶
Match failures with errors.Is against ErrUnauthorized, ErrUnprocessable, ErrRateLimited, ErrOverloaded, and ErrServer. For the raw response, use errors.As to reach *Error, which keeps the status code and the body.
Index ¶
Constants ¶
const ( // DefaultBaseURL is the TypeSafe API root. DefaultBaseURL = "https://api.typesafe.ai" // DefaultModel is the alias for the current stable Jev release. DefaultModel = "jev-latest" // DefaultTimeout bounds one HTTP attempt, not the whole retried call. // The retry budget stops the loop before a backoff that would cross // it, but the attempt that follows the backoff still runs, so a call // can overrun the budget by up to one attempt's timeout. DefaultTimeout = 10 * time.Second )
Documented defaults.
const ( EnvAPIKey = "TYPESAFE_API_KEY" EnvBaseURL = "TYPESAFE_BASE_URL" EnvModel = "TYPESAFE_DEFAULT_MODEL" )
Environment variables New reads when the matching option is absent.
const Version = "0.1.0"
Version is this SDK's version, reported in the User-Agent header.
Variables ¶
var ( ErrUnprocessable = errors.New("typesafe: unprocessable entity") ErrRateLimited = errors.New("typesafe: rate limited") ErrOverloaded = errors.New("typesafe: overloaded") ErrServer = errors.New("typesafe: server error") ErrNoAnswer = errors.New("typesafe: no answer for question id") ErrWrongType = errors.New("typesafe: answer type does not match question type") ErrIncompleteAnswer = errors.New("typesafe: answer is missing a required field") ErrUnexpectedOption = errors.New("typesafe: answer holds an option the question did not declare") ErrNoAPIKey = errors.New("typesafe: no API key") ErrNoQuestions = errors.New("typesafe: no questions") ErrNilQuestion = errors.New("typesafe: nil question") ErrEmptyID = errors.New("typesafe: empty question id") ErrDuplicateID = errors.New("typesafe: duplicate question id") ErrNoOptions = errors.New("typesafe: choice has no options") ErrTooFewLevels = errors.New("typesafe: score needs at least two levels") )
Sentinel errors. Match these with errors.Is rather than comparing status codes at the call site.
Functions ¶
This section is empty.
Types ¶
type ChoiceAnswer ¶
type ChoiceAnswer[T ~string] struct { // Value is the highest-probability option. Value T // Probabilities maps every option to its probability. They sum to 1. Probabilities map[T]float64 // Confidence is how certain the model is, derived from Probabilities. Confidence float64 }
ChoiceAnswer is one Choice result, in the caller's own option type.
type ChoiceQuestion ¶
type ChoiceQuestion[T ~string] struct { // contains filtered or unexported fields }
ChoiceQuestion picks one option from a set you define. Build one with Choice.
func Choice ¶
func Choice[T ~string](id string, instructions Entry, options Opts[T]) ChoiceQuestion[T]
Choice picks one option from a set you define. Declare T as your own string type and the compiler will reject any option you write outside it; what the server sends back is checked at read time, where From fails with an error wrapping ErrUnexpectedOption if the answer holds an option the question never declared.
func (ChoiceQuestion[T]) From ¶
func (q ChoiceQuestion[T]) From(r *Result) (ChoiceAnswer[T], error)
From reads this question's answer, typed as the option type you declared. An answer body missing choice, probabilities, or confidence reports ErrIncompleteAnswer; an empty probabilities map counts as missing, since the API documents it as summing to 1.
Both the winning choice and every key in Probabilities are checked against the options the question declared: the compiler checks what you write, only this method can check what the server sends back. An option that was never declared fails with an error wrapping ErrUnexpectedOption rather than decoding into a value that equals none of your constants.
func (ChoiceQuestion[T]) ID ¶
func (q ChoiceQuestion[T]) ID() string
ID is the key this question's answer comes back under.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the TypeSafe API. It is safe for concurrent use.
func New ¶
New builds a client.
Configuration resolves in three layers, later winning over earlier: documented defaults, then TYPESAFE_API_KEY, TYPESAFE_BASE_URL, and TYPESAFE_DEFAULT_MODEL, then the options you pass.
An API key is the one hard requirement; without one New returns ErrNoAPIKey rather than failing later on the first call.
func (*Client) Ask ¶
Ask evaluates state against every question in one request.
State is any JSON-encodable value: a string for plain text, or a struct, map, or slice for a chat log or a record. Questions are answered independently and in parallel; none of them can see another's answer, so chain a second Ask when one judgment depends on another.
Ask retries transient failures, and a retry can bill twice for one evaluation: the client cannot tell a request that never reached the server from one whose response was lost after the server already processed it, so it retries either one. If you need at-most-once behavior, set WithRetry to a policy with MaxRetries: 0.
Read each answer through the question value you passed in:
urgent := typesafe.Noul("is_urgent", "Does this convey urgency?")
res, err := client.Ask(ctx, ticket, urgent)
p, err := urgent.From(res)
type Entry ¶
type Entry = any
Entry is any JSON-encodable value the API accepts where structure is allowed: a string, an object, or an array.
Instructions, Choice option descriptions, Score level descriptions, and Noul true/false criteria all take this shape. Structured entries let you hand the model a rubric instead of a sentence.
Upstream documents null as valid only for a Choice option's description (criteria is map<string, string | null>: null means the option needs no extra detail). Nothing in the docs says null is valid for Instructions, Score levels, or Noul true/false criteria.
type Error ¶
Error is an HTTP-level failure from the TypeSafe API.
TypeSafe documents its status codes but not the shape of its error bodies, so Body carries the response verbatim and Message is a best-effort guess. Read Body when you need certainty.
type Model ¶
type Model struct {
// Name is what you pass to WithModel, such as jev-latest.
Name string `json:"name"`
Description string `json:"description"`
// ReleaseDate is the API's value, unparsed. Its format is not
// documented, so turning it into a time.Time would be a guess.
ReleaseDate string `json:"release_date"`
}
Model is one model or alias the account can use.
type NoulQuestion ¶
type NoulQuestion struct {
// contains filtered or unexported fields
}
NoulQuestion asks a yes/no question. Build one with Noul.
func Noul ¶
func Noul(id string, instructions Entry) NoulQuestion
Noul asks a yes/no question. The answer is the probability of yes, from 0 to 1. A Noul carries no confidence: the probability is the whole answer.
func (NoulQuestion) From ¶
func (q NoulQuestion) From(r *Result) (float64, error)
From reads this question's answer: the probability of yes, from 0 to 1. An answer body that omits or nulls the noul field reports ErrIncompleteAnswer instead of decoding to 0.
A Noul carries no confidence field. The probability is the whole answer: 0.5 means the model is genuinely torn, not that it is unsure.
func (NoulQuestion) ID ¶
func (q NoulQuestion) ID() string
ID is the key this question's answer comes back under.
func (NoulQuestion) WithCriteria ¶
func (q NoulQuestion) WithCriteria(yes, no Entry) NoulQuestion
WithCriteria describes what a yes and a no mean, which sharpens the probability. It returns a copy; question values never change in place.
type Option ¶
Option configures a Client. Pass options to New.
func WithAPIKey ¶
WithAPIKey sets the key, overriding TYPESAFE_API_KEY.
func WithBaseURL ¶
WithBaseURL points the client somewhere other than api.typesafe.ai, which is what you want for a proxy or a record-and-replay test server.
func WithHTTPClient ¶
WithHTTPClient supplies your own transport, for proxies, custom TLS, or instrumentation. The client is used as given and never modified.
func WithModel ¶
WithModel sets the model sent with every request, overriding TYPESAFE_DEFAULT_MODEL. Call Models to see what your account can use.
func WithRetry ¶
func WithRetry(p RetryPolicy) Option
WithRetry replaces the retry policy. Start from DefaultRetryPolicy and adjust; the zero RetryPolicy retries nothing.
Statuses is copied, so the caller may keep using the slice it passed: editing it or appending into its spare capacity does not retune the client.
func WithTimeout ¶
WithTimeout bounds each HTTP attempt. It applies to a copy of the HTTP client, so it works alongside WithHTTPClient in either order.
type Opts ¶
Opts maps each Choice option to its description. A nil description means the option needs no extra detail.
type Question ¶
type Question interface {
// ID is the key this question's answer comes back under.
ID() string
// contains filtered or unexported methods
}
Question is one typed judgment in a request.
The interface is sealed by its unexported methods: only Noul, Choice, and Score satisfy it, because the API accepts nothing else.
type Result ¶
type Result struct {
// Model is the model that performed the evaluation. When you send an
// alias like jev-latest, this is the concrete version it resolved to.
Model string
Usage Usage
// contains filtered or unexported fields
}
Result holds the answers to one Ask.
Read an answer through the question value that asked it — urgent.From(res) — rather than by key. Each handle's From fixes the answer's shape at compile time: NoulQuestion.From returns float64 and nothing else, and ChoiceQuestion[T].From returns ChoiceAnswer[T].
The compiler cannot check the id, though. Two handles may share an id and disagree about its type, and that read compiles; it fails at read time with ErrWrongType rather than decoding a zero value.
func (*Result) RawAnswer ¶
func (r *Result) RawAnswer(id string) (json.RawMessage, error)
RawAnswer returns the exact wire bytes the API sent for one answer, keyed by question id, with no decoding or type checking applied. The caller owns the returned copy: mutating it cannot affect the Result, so raw answer evidence can be retained and logged without sharing mutable state with the typed readers.
This is the supported way to keep exact raw answer evidence — the bytes the API returned, not a re-encoding of them. A question id the response did not carry reports ErrNoAnswer.
type RetryPolicy ¶
type RetryPolicy struct {
// MaxRetries is how many attempts follow the first one.
MaxRetries int
// BackoffInitial is the delay before the first retry. It doubles each
// time, up to BackoffMax.
BackoffInitial time.Duration
// BackoffMax caps the computed delay. Retry-After can still exceed it.
// Zero means no cap: the delay doubles until it saturates at the
// largest representable duration.
BackoffMax time.Duration
// BackoffJitter is the fraction of the delay that may be subtracted at
// random, spreading out a thundering herd. 0.25 means up to 25% off.
BackoffJitter float64
// Statuses lists the response codes worth retrying.
Statuses []int
// RespectRetryAfter honors a Retry-After or retry-after-ms header in
// preference to the computed delay.
RespectRetryAfter bool
// Budget caps the total wall time of a call, delays included. Zero
// means no cap.
Budget time.Duration
}
RetryPolicy controls how transient failures are retried.
The zero value retries nothing. Start from DefaultRetryPolicy and adjust.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
DefaultRetryPolicy mirrors the documented defaults of TypeSafe's Python SDK: two retries, 500ms doubling to 5s, 25% jitter, 30s budget, retrying 408, 429, and every 5xx.
func (RetryPolicy) Retryable ¶
func (p RetryPolicy) Retryable(status int) bool
Retryable reports whether a response code is worth another attempt.
type ScoreAnswer ¶
type ScoreAnswer struct {
// Value is probability-weighted across the levels and can land between
// two of them: 1.6 sits closer to level 2 than to level 1.
Value float64
// Legend maps each level index back to the description you supplied.
Legend map[int]string
// Probabilities maps each level index to its probability. They sum to 1.
Probabilities map[int]float64
// Confidence is how certain the model is, derived from Probabilities.
Confidence float64
}
ScoreAnswer is one Score result.
type ScoreQuestion ¶
type ScoreQuestion struct {
// contains filtered or unexported fields
}
ScoreQuestion rates the state against ordered levels. Build one with Score.
func Score ¶
func Score(id string, instructions Entry, levels ...Entry) ScoreQuestion
Score rates the state against levels given lowest first. The answer is probability-weighted across them and can land between two levels. The API requires at least two.
func (ScoreQuestion) From ¶
func (q ScoreQuestion) From(r *Result) (ScoreAnswer, error)
From reads this question's answer, with the wire format's string level keys converted back to the integer indexes you passed levels in. An answer body missing score, legend, probabilities, or confidence reports ErrIncompleteAnswer; empty legend or probabilities maps count as missing.
func (ScoreQuestion) ID ¶
func (q ScoreQuestion) ID() string
ID is the key this question's answer comes back under.