Documentation
¶
Overview ¶
Package typesafe is a Go client for the TypeSafe AI API.
TypeSafe's System One model, Jev, evaluates typed questions against a state and returns structured answers instead of generated text: a probability for a yes/no (Noul) question, a selected option with a full probability distribution for a Choice question, and a probability-weighted position on an ordered rubric for a Score question. Every question in a request is evaluated independently against the same state, so ask all the questions your code might need in one call.
Create a client, then send a state and a map of named questions:
client, err := typesafe.NewClient() // reads TYPESAFE_API_KEY
if err != nil {
log.Fatal(err)
}
resp, err := client.SystemOne(ctx, typesafe.SystemOneRequest{
State: "Help! My payouts have been failing for 3 days.",
Questions: typesafe.Questions{
"department": typesafe.Choice("Which team should handle this?", map[string]any{
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": "Pricing, upgrades, new accounts",
}),
"frustration": typesafe.Score("How frustrated is the customer?",
"Calm", "Frustrated", "Very angry"),
"is_urgent": typesafe.Noul("Does this convey urgency?"),
},
})
if err != nil {
log.Fatal(err)
}
department, _ := resp.Choice("department")
frustration, _ := resp.Score("frustration")
urgent, _ := resp.Noul("is_urgent")
fmt.Println(department.Choice, department.Confidence)
fmt.Println(frustration.Score, frustration.Label())
fmt.Println(urgent.IsTrue(0.8))
The client retries rate-limited (429), overloaded (529), and other transient failures with exponential backoff, honoring Retry-After headers. Non-2xx responses that survive retries are returned as an *APIError.
See https://docs.typesafe.ai for the API documentation.
Example ¶
package main
import (
"context"
"fmt"
"log"
typesafe "github.com/adibhanna/typesafego"
)
func main() {
client, err := typesafe.NewClient() // reads TYPESAFE_API_KEY
if err != nil {
log.Fatal(err)
}
resp, err := client.SystemOne(context.Background(), typesafe.SystemOneRequest{
State: "My payment failed twice and I still got charged. Fix this now!",
Questions: typesafe.Questions{
"is_urgent": typesafe.Noul("Does the customer convey urgency?"),
"department": typesafe.ChoiceOf("Which team should handle this?", "billing", "technical", "sales"),
"frustration": typesafe.Score("How frustrated is the customer?", "Calm", "Frustrated", "Very angry"),
},
})
if err != nil {
log.Fatal(err)
}
urgent, _ := resp.Noul("is_urgent")
department, _ := resp.Choice("department")
frustration, _ := resp.Score("frustration")
fmt.Printf("urgent: %v (p=%.2f)\n", urgent.IsTrue(0.5), urgent.Noul)
fmt.Printf("department: %s (confidence %.2f)\n", department.Choice, department.Confidence)
fmt.Printf("frustration: %.2f → %v\n", frustration.Score, frustration.Label())
}
Output:
Index ¶
- Constants
- Variables
- func DefaultShouldRetryStatus(status int) bool
- type APIError
- type Answer
- type Answers
- type ChoiceAnswer
- type ChoiceQuestion
- type Client
- type ListModelsResponse
- type ModelInfo
- type NoulAnswer
- type NoulCriteria
- type NoulQuestion
- type Option
- func WithAPIKey(key string) Option
- func WithBaseURL(baseURL string) Option
- func WithHTTPClient(httpClient *http.Client) Option
- func WithHeader(key, value string) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxRetries(n int) Option
- func WithModel(model string) Option
- func WithRetryPolicy(policy RetryPolicy) Option
- func WithTimeout(d time.Duration) Option
- type Question
- type QuestionType
- type Questions
- type RankedOption
- type RawQuestion
- type RetryPolicy
- type ScoreAnswer
- func (a *ScoreAnswer) Label() any
- func (a *ScoreAnswer) LabelOf(level int) any
- func (a *ScoreAnswer) Level() int
- func (a *ScoreAnswer) MarshalJSON() ([]byte, error)
- func (a *ScoreAnswer) MaxLevel() int
- func (a *ScoreAnswer) NearestLevel() int
- func (a *ScoreAnswer) Normalized() float64
- func (a *ScoreAnswer) ProbabilityOf(level int) float64
- func (*ScoreAnswer) Type() QuestionType
- func (a *ScoreAnswer) UnmarshalJSON(data []byte) error
- type ScoreQuestion
- type SystemOneRequest
- type SystemOneResponse
- type UnknownAnswer
- type Usage
Examples ¶
Constants ¶
const ( // DefaultBaseURL is the TypeSafe API root. DefaultBaseURL = "https://api.typesafe.ai" // DefaultModel is the model used when a request does not name one. // It is an alias for the most recent stable Jev release. DefaultModel = ModelJevLatest // DefaultTimeout is the default timeout for a single HTTP attempt. // Retries each get their own timeout; use the context deadline to bound // the whole call. DefaultTimeout = 10 * time.Second )
Defaults used when neither an Option nor an environment variable sets a value.
const ( // EnvAPIKey holds the API key. EnvAPIKey = "TYPESAFE_API_KEY" // EnvBaseURL overrides the API root. EnvBaseURL = "TYPESAFE_BASE_URL" // EnvDefaultModel overrides the default model. EnvDefaultModel = "TYPESAFE_DEFAULT_MODEL" )
Environment variables read by NewClient. Explicit options take precedence over environment variables; empty or whitespace-only values are ignored.
const ( // ModelJevLatest is the most recent stable, official Jev release. ModelJevLatest = "jev-latest" // ModelJevPreview is the most recent release, whether or not it is an // official one. ModelJevPreview = "jev-preview" )
Model aliases accepted by the API's model field. An alias moves when a new release ships; pin a versioned ID such as "jev-1.13.0" (see https://docs.typesafe.ai/models) if you have tuned thresholds against a specific version. The response's Model field reports the versioned ID that answered.
const StatusOverloaded = 529
StatusOverloaded is the non-standard status the API returns when it is temporarily overloaded.
const Version = "0.1.0"
Version is the version of this library, sent in the User-Agent header.
Variables ¶
var ( // ErrMissingAPIKey is returned by NewClient when no API key is configured. ErrMissingAPIKey = errors.New("typesafe: missing API key: set " + EnvAPIKey + " or use WithAPIKey") // ErrInvalidRequest is returned before a request is sent when it fails // local validation, such as a missing state or an empty question set. ErrInvalidRequest = errors.New("typesafe: invalid request") // ErrInvalidResponse is returned when a 2xx response body cannot be // decoded. ErrInvalidResponse = errors.New("typesafe: invalid response") // ErrAnswerNotFound is returned by the typed answer getters when no answer // has the requested id. ErrAnswerNotFound = errors.New("typesafe: answer not found") // ErrAnswerType is returned by the typed answer getters when the answer // has a different type than requested. ErrAnswerType = errors.New("typesafe: unexpected answer type") )
Sentinel errors. Match them with errors.Is; they are wrapped with detail about what went wrong.
Functions ¶
func DefaultShouldRetryStatus ¶
DefaultShouldRetryStatus reports whether a status is retried by default: 408 Request Timeout, 429 Too Many Requests, and every 5xx status, including 529 Overloaded.
Types ¶
type APIError ¶
type APIError struct {
// StatusCode is the HTTP status code.
StatusCode int
// Method and URL identify the request. The URL never includes credentials.
Method string
URL string
// Message is the human-readable error extracted from the response body,
// or "" if none was found.
Message string
// Body is the raw response body.
Body []byte
// Header holds the HTTP response headers.
Header http.Header
// RequestID is the x-typesafe-request-id response header, useful when
// contacting support. Empty if the server did not send one.
RequestID string
}
APIError is returned when the API responds with a non-2xx status after any retries. Match it with errors.As:
var apiErr *typesafe.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests {
// rate limited
}
Status codes documented by the API: 401 (bad API key), 422 (request failed validation; Message names the field), 429 (rate limited) and 529 (overloaded). The client retries 429, 529 and other transient statuses by default; see RetryPolicy.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
typesafe "github.com/adibhanna/typesafego"
)
func main() {
client, err := typesafe.NewClient(typesafe.WithAPIKey("sk-invalid"))
if err != nil {
log.Fatal(err)
}
_, err = client.SystemOne(context.Background(), typesafe.SystemOneRequest{
State: "hello",
Questions: typesafe.Questions{"q": typesafe.Noul("Is this a greeting?")},
})
var apiErr *typesafe.APIError
switch {
case errors.As(err, &apiErr) && apiErr.StatusCode == 401:
fmt.Println("bad API key; request id", apiErr.RequestID)
case errors.As(err, &apiErr) && apiErr.StatusCode == 429:
delay, _ := apiErr.RetryAfter()
fmt.Println("rate limited; retry after", delay)
case errors.Is(err, typesafe.ErrInvalidRequest):
fmt.Println("fix the request:", err)
case err != nil:
fmt.Println("transport or decoding failure:", err)
}
}
Output:
func (*APIError) Error ¶
Error formats the error as "typesafe: POST https://api.typesafe.ai/v1/systemone: 429 Too Many Requests: <message> (request id <id>)".
type Answer ¶
type Answer interface {
json.Marshaler
// Type returns the answer's wire type, which matches its question's.
Type() QuestionType
// contains filtered or unexported methods
}
Answer is the typed result of one question.
The concrete types are *NoulAnswer, *ChoiceAnswer and *ScoreAnswer, plus *UnknownAnswer for answer types this package does not know about. Use a type switch, or the typed getters on Answers and SystemOneResponse. The interface is sealed: only this package's types implement it.
type Answers ¶
Answers maps question ids to answers, under the same ids used in the request. Values are *NoulAnswer, *ChoiceAnswer, *ScoreAnswer or *UnknownAnswer, never nil.
func (Answers) Choice ¶
func (a Answers) Choice(id string) (*ChoiceAnswer, error)
Choice returns the choice answer with the given id. The error wraps ErrAnswerNotFound if the id is missing and ErrAnswerType if the answer is of another type.
func (Answers) Noul ¶
func (a Answers) Noul(id string) (*NoulAnswer, error)
Noul returns the yes/no answer with the given id. The error wraps ErrAnswerNotFound if the id is missing and ErrAnswerType if the answer is of another type.
func (Answers) Score ¶
func (a Answers) Score(id string) (*ScoreAnswer, error)
Score returns the score answer with the given id. The error wraps ErrAnswerNotFound if the id is missing and ErrAnswerType if the answer is of another type.
func (*Answers) UnmarshalJSON ¶
UnmarshalJSON decodes a map of answers, dispatching on each answer's "type". Unknown types decode to *UnknownAnswer so that new answer types do not break existing code, and a null answer is left out of the map.
type ChoiceAnswer ¶
type ChoiceAnswer struct {
// Choice is the option with the highest probability.
Choice string `json:"choice"`
// Probabilities maps every option to its probability. They sum to 1.
Probabilities map[string]float64 `json:"probabilities"`
// Confidence, from 0 to 1, is how certain the model is about Choice. It
// is derived from the probability distribution; see
// https://docs.typesafe.ai/confidence.
Confidence float64 `json:"confidence"`
}
ChoiceAnswer answers a ChoiceQuestion.
func (*ChoiceAnswer) MarshalJSON ¶
func (a *ChoiceAnswer) MarshalJSON() ([]byte, error)
MarshalJSON encodes the answer in the API's wire format.
func (*ChoiceAnswer) ProbabilityOf ¶
func (a *ChoiceAnswer) ProbabilityOf(option string) float64
ProbabilityOf returns the probability of the given option, or 0 if the option is not in the answer.
func (*ChoiceAnswer) Ranked ¶
func (a *ChoiceAnswer) Ranked() []RankedOption
Ranked returns every option ordered from most to least probable. Options with equal probability are ordered by name.
type ChoiceQuestion ¶
type ChoiceQuestion struct {
// Instructions says what the model should decide. It may be a string or
// JSON structure.
Instructions any
// Options maps each option name to its description. A description may be
// a string, JSON structure, or nil when the name speaks for itself. It is
// sent as the question's "criteria".
Options map[string]any
}
ChoiceQuestion picks one option from a set you define. The answer is a *ChoiceAnswer with the selected option, the probability of every option, and a confidence.
func Choice ¶
func Choice(instructions any, options map[string]any) ChoiceQuestion
Choice builds a question that selects one of the given options. Each option maps to a description; use nil for options that need none.
typesafe.Choice("Which team should handle this?", map[string]any{
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": nil,
})
Example (Structured) ¶
package main
import (
typesafe "github.com/adibhanna/typesafego"
)
func main() {
// Instructions and option descriptions may be any JSON value, not only
// strings, so a rubric can be given as structured data.
question := typesafe.Choice(
map[string]any{
"task": "Route the ticket to the right team.",
"context": "The company sells developer tools.",
},
map[string]any{
"billing": "Payments, invoices, refunds and subscription changes.",
"technical": map[string]any{"handles": []string{"bugs", "outages", "API errors"}},
"sales": nil, // no description
},
)
_ = typesafe.Questions{"department": question}
}
Output:
func ChoiceOf ¶
func ChoiceOf(instructions any, options ...string) ChoiceQuestion
ChoiceOf builds a question that selects one of the named options, none of which carries a description.
typesafe.ChoiceOf("What is the customer's sentiment?", "positive", "neutral", "negative")
func (ChoiceQuestion) MarshalJSON ¶
func (q ChoiceQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question in the API's wire format.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client calls the TypeSafe API. Create one with NewClient and share it; it is safe for concurrent use.
func NewClient ¶
NewClient returns a client configured from the environment and opts.
The API key comes from WithAPIKey or the TYPESAFE_API_KEY environment variable; NewClient returns ErrMissingAPIKey if neither is set. The base URL and default model come from WithBaseURL and WithModel, then from TYPESAFE_BASE_URL and TYPESAFE_DEFAULT_MODEL, then from DefaultBaseURL and DefaultModel. Retries use DefaultRetryPolicy unless WithRetryPolicy or WithMaxRetries says otherwise.
Example (Options) ¶
package main
import (
"context"
"log"
"time"
typesafe "github.com/adibhanna/typesafego"
)
func main() {
policy := typesafe.DefaultRetryPolicy()
policy.MaxRetries = 4
client, err := typesafe.NewClient(
typesafe.WithAPIKey("sk-..."),
typesafe.WithModel(typesafe.ModelJevPreview),
typesafe.WithTimeout(20*time.Second),
typesafe.WithRetryPolicy(policy),
typesafe.WithHeader("X-Request-Source", "billing-worker"),
)
if err != nil {
log.Fatal(err)
}
// Options can also be applied to a single call without changing the client.
_, _ = client.SystemOne(context.Background(), typesafe.SystemOneRequest{
State: "hello",
Questions: typesafe.Questions{"q": typesafe.Noul("Is this a greeting?")},
}, typesafe.WithModel("jev-1.13.0"), typesafe.WithMaxRetries(0))
}
Output:
func (*Client) BaseURL ¶
BaseURL returns the API root requests are sent to, without a trailing slash.
func (*Client) ListModels ¶
ListModels returns the models and aliases your account can name in a request. opts override the client's settings for this call only.
func (*Client) SystemOne ¶
func (c *Client) SystemOne(ctx context.Context, req SystemOneRequest, opts ...Option) (*SystemOneResponse, error)
SystemOne evaluates req.State against req.Questions and returns one answer per question. Every question is evaluated independently against the same state, so ask everything you might need in one call.
The request is validated locally first (see Questions.Validate); failures wrap ErrInvalidRequest. Transient failures are retried according to the retry policy; a retry whose delay would run past ctx's deadline is skipped and the last failure returned instead. A non-2xx response that survives retries is returned as an *APIError; transport failures are returned wrapped, so errors.Is(err, context.DeadlineExceeded) detects a timeout. opts override the client's settings for this call only.
Example (ConfidenceGating) ¶
package main
import (
"context"
"fmt"
"log"
typesafe "github.com/adibhanna/typesafego"
)
func main() {
client, err := typesafe.NewClient()
if err != nil {
log.Fatal(err)
}
resp, err := client.SystemOne(context.Background(), typesafe.SystemOneRequest{
State: "Can I get my money back? The app crashed during checkout.",
Questions: typesafe.Questions{"department": typesafe.ChoiceOf("Which team?", "billing", "technical")},
})
if err != nil {
log.Fatal(err)
}
department, err := resp.Choice("department")
if err != nil {
log.Fatal(err)
}
if department.Confidence < 0.7 {
fmt.Println("route to a human:", department.Ranked())
return
}
fmt.Println("route to", department.Choice)
}
Output:
type ListModelsResponse ¶
type ListModelsResponse struct {
// Models lists the names your account can send in the model field. It
// currently lists the aliases; versioned IDs such as "jev-1.13.0" are
// accepted whether or not they appear here.
Models []ModelInfo `json:"models"`
// RequestID is the x-typesafe-request-id response header, if any.
RequestID string `json:"-"`
// Header holds the HTTP response headers.
Header http.Header `json:"-"`
}
ListModelsResponse is the result of Client.ListModels.
type ModelInfo ¶
type ModelInfo struct {
// Name is the ID or alias accepted by the request's model field.
Name string `json:"name"`
// Description says what the model is for.
Description string `json:"description"`
// ReleaseDate is when the model or alias was released.
ReleaseDate string `json:"release_date"`
}
ModelInfo describes a model or alias available to your account.
type NoulAnswer ¶
type NoulAnswer struct {
// Noul is the probability that the answer is yes, from 0 (no) to 1 (yes).
Noul float64 `json:"noul"`
}
NoulAnswer answers a NoulQuestion.
func (*NoulAnswer) IsTrue ¶
func (a *NoulAnswer) IsTrue(threshold float64) bool
IsTrue reports whether the probability of yes is at least threshold. Choose the threshold for the cost of being wrong: 0.5 treats the answer as a plain boolean; a higher value such as 0.8 acts only when the model is sure.
func (*NoulAnswer) MarshalJSON ¶
func (a *NoulAnswer) MarshalJSON() ([]byte, error)
MarshalJSON encodes the answer in the API's wire format.
type NoulCriteria ¶
type NoulCriteria struct {
// True describes what a yes (a value near 1) means.
True any `json:"true,omitzero"`
// False describes what a no (a value near 0) means.
False any `json:"false,omitzero"`
}
NoulCriteria optionally describes what a yes and a no mean for a NoulQuestion. Either field may be a string, an object, an array, or nil to leave it out.
type NoulQuestion ¶
type NoulQuestion struct {
// Instructions is the yes/no question to evaluate. It may be a string or
// JSON structure (an object or array); see
// https://docs.typesafe.ai/primitives/advanced.
Instructions any
// Criteria optionally describes what a yes and a no mean. It is omitted
// from the request when nil or empty.
Criteria *NoulCriteria
}
NoulQuestion is a yes/no question. The answer is a *NoulAnswer holding the probability that the answer is yes.
func Noul ¶
func Noul(instructions any) NoulQuestion
Noul builds a yes/no question.
typesafe.Noul("Does this convey urgency?")
func (NoulQuestion) MarshalJSON ¶
func (q NoulQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question in the API's wire format.
func (NoulQuestion) WithCriteria ¶
func (q NoulQuestion) WithCriteria(yes, no any) NoulQuestion
WithCriteria returns a copy of the question that describes what a yes and a no mean. Pass nil for either side to leave it out.
typesafe.Noul("Does this convey urgency?").
WithCriteria("Explicitly time-sensitive", "No urgency expressed")
Example ¶
package main
import (
typesafe "github.com/adibhanna/typesafego"
)
func main() {
question := typesafe.Noul("Is the customer asking for a refund?").
WithCriteria(
"The customer explicitly asks for money back or a chargeback.",
"The customer asks about anything else, including billing questions without a refund request.",
)
_ = typesafe.Questions{"is_refund": question}
}
Output:
type Option ¶
type Option func(*config) error
Option configures a Client when passed to NewClient, or overrides the client's settings for one call when passed to Client.SystemOne or Client.ListModels.
func WithAPIKey ¶
WithAPIKey sets the API key sent in the Authorization header. It takes precedence over the TYPESAFE_API_KEY environment variable. An empty key is ignored.
func WithBaseURL ¶
WithBaseURL sets the API root, such as "https://api.typesafe.ai" or the address of a proxy or test server. Paths are appended to it: the evaluation endpoint is <base>/v1/systemone. It takes precedence over the TYPESAFE_BASE_URL environment variable. An empty URL is ignored.
func WithHTTPClient ¶
WithHTTPClient sets the *http.Client used to send requests. The default is http.DefaultClient. Per-attempt timeouts are applied through the request context, so the client's own Timeout is not needed. A nil client is ignored.
func WithHeader ¶
WithHeader adds a header to every request. Calling it again with the same key replaces the earlier value. The Authorization, Content-Type and Accept headers are managed by the client and cannot be overridden; User-Agent can.
func WithLogger ¶
WithLogger sets the logger. Requests and responses are logged at Debug level and retries at Info level; API keys and bodies are never logged. The default discards everything. A nil logger is ignored.
func WithMaxRetries ¶
WithMaxRetries sets the number of retries after the first attempt, keeping the rest of the retry policy. 0 disables retries.
func WithModel ¶
WithModel sets the model used by requests that do not name one, such as "jev-latest" or a pinned version like "jev-1.13.0". It takes precedence over the TYPESAFE_DEFAULT_MODEL environment variable. An empty model is ignored.
func WithRetryPolicy ¶
func WithRetryPolicy(policy RetryPolicy) Option
WithRetryPolicy replaces the retry policy. Start from DefaultRetryPolicy and adjust it; the zero RetryPolicy disables retries.
func WithTimeout ¶
WithTimeout sets the timeout for a single HTTP attempt, including reading the response body. The default is DefaultTimeout (10s). Each retry gets a fresh timeout; bound the whole call with the context deadline. 0 disables the per-attempt timeout.
type Question ¶
type Question interface {
json.Marshaler
// Type returns the question's wire type.
Type() QuestionType
}
Question is a typed question sent in the questions map of a request.
The three built-in implementations are NoulQuestion, ChoiceQuestion and ScoreQuestion, normally built with Noul, Choice, ChoiceOf and Score. RawQuestion sends an arbitrary JSON object for anything the typed forms do not cover.
type QuestionType ¶
type QuestionType string
QuestionType identifies the kind of a Question and of the Answer it produces.
const ( // TypeNoul is a yes/no question answered with the probability of yes. TypeNoul QuestionType = "noul" // TypeChoice selects one option from a set and returns the full // probability distribution over the options. TypeChoice QuestionType = "choice" // TypeScore rates the state against an ordered rubric and returns a // probability-weighted position on it. TypeScore QuestionType = "score" )
The three System One question types.
type Questions ¶
Questions maps question ids to questions. You choose the ids; answers come back under the same ids. The id is not shown to the model.
func (Questions) Validate ¶
Validate reports the first problem found in the question set: an empty set, a nil question, a ChoiceQuestion without options, a ScoreQuestion with fewer than two levels, or a RawQuestion without a "type". Errors wrap ErrInvalidRequest. Client.SystemOne calls Validate before sending.
type RankedOption ¶
RankedOption is an option paired with its probability.
type RawQuestion ¶
RawQuestion sends an arbitrary JSON object as a question. Use it for fields or question types this package does not model. It must contain a string "type".
typesafe.RawQuestion{
"type": "noul",
"instructions": "Is this spam?",
}
func (RawQuestion) MarshalJSON ¶
func (q RawQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the map as a JSON object.
func (RawQuestion) Type ¶
func (q RawQuestion) Type() QuestionType
Type returns the question's "type" entry, or "" if it is missing or not a string.
type RetryPolicy ¶
type RetryPolicy struct {
// MaxRetries is the number of retries after the first attempt. 0 disables
// retries.
MaxRetries int
// InitialBackoff is the delay before the first retry. It doubles on every
// retry up to MaxBackoff.
InitialBackoff time.Duration
// MaxBackoff caps the computed backoff delay.
MaxBackoff time.Duration
// Jitter is the fraction of each backoff delay to randomly subtract, from
// 0 (none) to 1.
Jitter float64
// ShouldRetryStatus reports whether a response status is worth retrying.
// nil means DefaultShouldRetryStatus.
ShouldRetryStatus func(status int) bool
// RespectRetryAfter honors retry-after-ms and Retry-After response
// headers instead of the computed backoff.
RespectRetryAfter bool
// MaxRetryAfter is the longest server-requested delay to honor; longer
// ones fall back to the computed backoff. Leaving it zero therefore
// ignores every non-zero Retry-After; DefaultRetryPolicy uses 60s.
MaxRetryAfter time.Duration
// RetryConnectionErrors retries attempts that fail before a response is
// read, such as refused connections, DNS failures, and interrupted bodies.
RetryConnectionErrors bool
// RetryTimeouts retries attempts that exceed the per-attempt timeout.
RetryTimeouts bool
}
RetryPolicy controls how the client retries failed attempts.
Start from DefaultRetryPolicy and change what you need; the zero value disables retries entirely. A retry waits min(InitialBackoff·2^n, MaxBackoff) reduced by up to Jitter, where n is the zero-based retry number, unless the response carries an acceptable retry-after-ms or Retry-After header.
Each attempt has its own timeout (see WithTimeout); there is no total budget for retries. Use the context deadline to bound the whole call.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() RetryPolicy
DefaultRetryPolicy returns the policy used by NewClient: 2 retries with backoff from 500ms doubling to 5s and 25% jitter, retrying HTTP 408, 429 and 5xx (including 529 Overloaded) as well as connection errors and timeouts, and honoring retry-after-ms or Retry-After headers of up to 60s.
type ScoreAnswer ¶
type ScoreAnswer struct {
// Score is the probability-weighted position on the rubric, from 0 to the
// highest level. It can fall between levels: 1.6 means mostly level 2
// with some weight on level 1.
Score float64
// Confidence, from 0 to 1, is how certain the model is about the score.
// It is derived from the probability distribution; see
// https://docs.typesafe.ai/confidence.
Confidence float64
// Legend holds the level descriptions from the question, indexed by
// level. Each entry is a string or the JSON structure you sent.
Legend []any
// Probabilities holds the probability of each level, indexed by level.
// They sum to 1. It is nil if the response omitted them.
Probabilities []float64
}
ScoreAnswer answers a ScoreQuestion.
The API keys legend and probabilities by level number as strings ("0", "1", ...). This type stores them as slices indexed by level, so Legend[i] describes level i and Probabilities[i] is its probability.
func (*ScoreAnswer) Label ¶
func (a *ScoreAnswer) Label() any
Label returns the legend entry for Level, or nil if the legend does not cover it.
func (*ScoreAnswer) LabelOf ¶
func (a *ScoreAnswer) LabelOf(level int) any
LabelOf returns the legend entry for the given level, or nil if the legend does not cover it.
func (*ScoreAnswer) Level ¶
func (a *ScoreAnswer) Level() int
Level returns the most probable level: the index of the highest probability, or the lowest such index on a tie. If the answer carries no probabilities it falls back to NearestLevel.
Level and NearestLevel differ when the distribution is split: with probabilities [0.5, 0, 0.5] the score is 1.0, so NearestLevel is 1 while Level is 0.
func (*ScoreAnswer) MarshalJSON ¶
func (a *ScoreAnswer) MarshalJSON() ([]byte, error)
MarshalJSON encodes the answer in the API's wire format, with legend and probabilities keyed by level number.
func (*ScoreAnswer) MaxLevel ¶
func (a *ScoreAnswer) MaxLevel() int
MaxLevel returns the highest level index, or -1 if the answer carries neither a legend nor probabilities.
func (*ScoreAnswer) NearestLevel ¶
func (a *ScoreAnswer) NearestLevel() int
NearestLevel returns Score rounded to the nearest level, clamped to the rubric's range.
func (*ScoreAnswer) Normalized ¶
func (a *ScoreAnswer) Normalized() float64
Normalized returns Score as a fraction of the highest level, from 0 to 1, so that scores from rubrics with different numbers of levels can be combined. It returns 0 for a rubric with fewer than two levels.
Example ¶
package main
import (
"fmt"
typesafe "github.com/adibhanna/typesafego"
)
func main() {
// Normalized puts scores from rubrics with different numbers of levels
// on a common 0..1 scale so they can be combined.
urgency := &typesafe.ScoreAnswer{Score: 2.4, Legend: []any{"Low", "Medium", "High", "Critical"}}
anger := &typesafe.ScoreAnswer{Score: 1.0, Legend: []any{"Calm", "Frustrated", "Very angry"}}
priority := 0.7*urgency.Normalized() + 0.3*anger.Normalized()
fmt.Printf("%.2f\n", priority)
}
Output: 0.71
func (*ScoreAnswer) ProbabilityOf ¶
func (a *ScoreAnswer) ProbabilityOf(level int) float64
ProbabilityOf returns the probability of the given level, or 0 if the level is out of range.
func (*ScoreAnswer) UnmarshalJSON ¶
func (a *ScoreAnswer) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the API's wire format, converting the string-keyed legend and probabilities into slices indexed by level. It fails with an error wrapping ErrInvalidResponse if a key is not a non-negative integer.
type ScoreQuestion ¶
type ScoreQuestion struct {
// Instructions says what the model should rate. It may be a string or
// JSON structure.
Instructions any
// Levels describes the rubric from lowest (index 0) to highest. Each level
// may be a string, JSON structure, or nil. At least two are required. They
// are sent as the question's "criteria".
Levels []any
}
ScoreQuestion rates the state along an ordered rubric. The answer is a *ScoreAnswer with a probability-weighted score that can fall between levels, the probability of every level, and a confidence.
func Score ¶
func Score(instructions any, levels ...any) ScoreQuestion
Score builds a question that rates the state against the given ordered levels, from lowest to highest.
typesafe.Score("How frustrated is the customer?", "Calm", "Frustrated", "Very angry")
func (ScoreQuestion) MarshalJSON ¶
func (q ScoreQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question in the API's wire format.
type SystemOneRequest ¶
type SystemOneRequest struct {
// State is the content to evaluate: a string for text, or any
// JSON-marshalable value (struct, map, slice) for structured data such as
// a chat log, a record, or your application's current state. Required.
State any
// Questions are the typed questions to evaluate against State, keyed by
// ids you choose. Required; see Questions.Validate for the rules.
Questions Questions
// Model overrides the client's default model for this request. Leave it
// empty to use the client's model.
Model string
// Extra holds additional top-level request fields to send as-is, for API
// parameters this package does not model. Entries named "state",
// "questions" or "model" are ignored in favor of the typed fields.
Extra map[string]any
}
SystemOneRequest is the input to Client.SystemOne.
func (SystemOneRequest) MarshalJSON ¶
func (r SystemOneRequest) MarshalJSON() ([]byte, error)
MarshalJSON encodes the request body sent to POST /v1/systemone. The model field is omitted when Model is empty; Client.SystemOne fills it in before encoding.
type SystemOneResponse ¶
type SystemOneResponse struct {
// Model is the versioned ID of the model that answered, such as
// "jev-1.13.0", even when the request named an alias.
Model string `json:"model"`
// Answers holds one answer per question, under the ids from the request.
Answers Answers `json:"answers"`
// Usage is the token usage of the request.
Usage Usage `json:"usage"`
// RequestID is the x-typesafe-request-id response header, useful when
// contacting support. Empty if the server did not send one.
RequestID string `json:"-"`
// Header holds the HTTP response headers.
Header http.Header `json:"-"`
}
SystemOneResponse is the result of Client.SystemOne.
It round-trips through encoding/json in the API's wire format, which makes it easy to build canned responses for tests.
func (*SystemOneResponse) Choice ¶
func (r *SystemOneResponse) Choice(id string) (*ChoiceAnswer, error)
Choice returns the choice answer with the given id. See Answers.Choice.
func (*SystemOneResponse) Noul ¶
func (r *SystemOneResponse) Noul(id string) (*NoulAnswer, error)
Noul returns the yes/no answer with the given id. See Answers.Noul.
func (*SystemOneResponse) Score ¶
func (r *SystemOneResponse) Score(id string) (*ScoreAnswer, error)
Score returns the score answer with the given id. See Answers.Score.
type UnknownAnswer ¶
type UnknownAnswer struct {
AnswerType QuestionType
Raw json.RawMessage
}
UnknownAnswer holds an answer whose type this package does not know. Raw is the answer's JSON as received.
func (*UnknownAnswer) MarshalJSON ¶
func (a *UnknownAnswer) MarshalJSON() ([]byte, error)
MarshalJSON returns the raw JSON as received.
func (*UnknownAnswer) Type ¶
func (a *UnknownAnswer) Type() QuestionType
Type returns the answer's wire type.