Documentation
¶
Overview ¶
Package typesafe is the official Go SDK for TypeSafe AI (https://typesafe.ai).
The TypeSafe AI platform evaluates deterministic classification and rating primitives through System One models. A Client sends one request that answers any number of named questions about a state, and returns every answer keyed by the name you supplied.
Primitives ¶
Three question kinds are supported:
- Noul: the probability that a statement holds, from 0 to 1.
- Choice: the best-matching criteria key, its confidence, and a probability per key.
- Score: an expected value across an ordered rubric whose levels start at 0.
Quickstart ¶
client, err := typesafe.NewClient() // reads TYPESAFE_API_KEY
if err != nil {
log.Fatal(err)
}
defer client.Close()
response, err := client.SystemOne(ctx,
map[string]any{"document": "I was charged twice. Please fix this ASAP."},
typesafe.Questions{
"billing": typesafe.NewNoul(
"Is this ticket about billing?",
typesafe.WithNoulCriteria(typesafe.NoulCriteria{
True: "Charges, refunds, or invoices",
False: "Anything else",
}),
),
"category": typesafe.NewChoice(
map[string]any{"billing": nil, "technical": nil, "other": nil},
typesafe.WithInstructions("What is this ticket about?"),
),
"urgency": typesafe.NewScore(
[]any{"can wait", "this week", "today"},
typesafe.WithInstructions("How urgent is this issue?"),
),
},
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("billing: %.2f\n", response.Nouls()["billing"].Noul)
fmt.Printf("category: %s (%.2f)\n", response.Choices()["category"].Choice, response.Choices()["category"].Confidence)
fmt.Printf("urgency: %.2f\n", response.Scores()["urgency"].Score)
Custom Response Models ¶
SystemOneAs decodes the payload into a caller-defined struct, so the compiler knows the shape of your answers. The struct may declare the envelope fields and one field per answer, named after the question:
type Ticket struct {
Model string `json:"model"`
Usage typesafe.Usage `json:"usage"`
Billing typesafe.NoulAnswer `json:"billing"`
Tone *typesafe.ChoiceAnswer `json:"tone"` // optional: a pointer can be absent
}
result, err := typesafe.SystemOneAs[Ticket](ctx, client, state, questions)
Alternatively, [SystemOneInto] populates an existing struct pointer in-place. A top-level field that cannot represent absence — anything but a pointer, map, slice, interface, function, or channel — is required, unless it is tagged omitempty or omitzero. A missing required field is reported as an APIResponseValidationError naming the field.
Patterns and Model Best Practices ¶
TypeSafe's Jev model is a calibrated System One engine. It excels at qualitative classification, judgment, and routing, but does not perform open-ended text generation or reliable numeric arithmetic.
1. Confidence-Gated Routing: Choice and Score answers include a statistical Confidence (0 to 1). A proven pattern is to tier actions. The bands below are an example: the boundaries are yours to choose, because they trade the cost of a wrong automatic action against the cost of a review. The docs gate at 0.5 for a high-stakes decision (https://docs.typesafe.ai/confidence) and at 0.6 for a moderation queue (https://docs.typesafe.ai/patterns/confidence-routing).
switch {
case answer.Confidence >= 0.85:
// High confidence: act automatically without human involvement
case answer.Confidence >= 0.50:
// Medium confidence: proceed with caution or ask for confirmation
default:
// Low confidence: model is unsure ("I don't know"); route to human triage
}
2. Counting & Speculative Fan-Out: Do not ask Jev to count items or compute sums. Keep arithmetic in Go: fan out atomic Noul questions across candidates in a single Client.SystemOne request, then aggregate the matching counts in Go.
3. Score Rubrics: Score questions require between 2 and 10 discrete rubric levels. The resulting ScoreAnswer.Score is an expected value suited for threshold filtering (e.g. score >= 1.5). Do not use Score as a continuous interpolator to reconstruct exact numeric quantities.
4. Choice Questions: Choice questions support up to 255 options per question.
5. What the SDK Validates: Questions are checked locally for the shape the request schema requires: a question type, criteria for a choice or a score question, at least one and at most 255 options for a choice, and between 2 and 10 levels for a score. Fields the schema makes optional — instructions above all — are sent only when the caller sets them; the HTTP reference page marks instructions required, but the request schema requires only criteria, for choice and score, so the SDK lets the API judge a question that omits them.
Errors ¶
Failures are typed and match with errors.As. Unsuccessful HTTP statuses map onto APIError and its status-specific subclasses (BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, UnprocessableEntityError, RateLimitError, OverloadedError, InternalServerError); each unwraps to APIError, so a single check finds both forms. OverloadedError (HTTP 529) and RateLimitError (HTTP 429) provide RetryAfter() for backoff delays. Transport failures are APIConnectionError or APITimeoutError, a malformed success body is APIResponseValidationError, and invalid configuration or input is SDKError. AsAPIError, AsRateLimitError, AsOverloadedError, AsTimeoutError, and AsValidationError are convenience extractors.
Configuration ¶
NewClient resolves configuration from its Option values first, then the environment (TYPESAFE_API_KEY, TYPESAFE_BASE_URL, TYPESAFE_DEFAULT_MODEL, TYPESAFE_LOG_LEVEL), then the SDK defaults. Client.SystemOne and ModelsService.List accept RequestOption values that override the client for a single call without mutating it: WithModel, WithCallTimeout, WithCallRetry, WithCallHeader, WithCallHeaders, and WithExtraBody.
Design ¶
The SDK is engineered for high concurrency and safety:
- Functional options: Option configures a Client, RequestOption a single call, and QuestionOption a single question.
- Resilient retries: jittered exponential backoff with an overall budget, honoring the retry-after-ms and Retry-After headers. See RetryPolicy and DefaultRetryPolicy.
- Security by design: credential headers are redacted from logs (NewRedactingHandler extends that to your own records), credentials in URLs are stripped from error endpoints, response bodies are bounded to 16 MB, and the SDK's transport refuses TLS below 1.2. Headers the SDK owns cannot be displaced by caller-supplied ones.
- Concurrency: a Client holds no per-request state and is safe for concurrent use; every call takes a context.Context for cancellation and deadlines.
- Modern Go: for range over integers, min and max, slices and maps helpers, and generics via SystemOneAs. Standard library only, and Go 1.26 or newer.
Index ¶
- Constants
- func DefaultRetryStatuses() map[int]bool
- func NormalizeQuestions(questions any) (map[string]any, error)
- func ParseLogLevel(level string) (slog.Level, bool)
- func RuntimeString() string
- func SystemOneAs[T any](ctx context.Context, client *Client, state any, questions any, ...) (*T, error)
- type APIConnectionError
- type APIError
- type APIResponseValidationError
- type APITimeoutError
- type Answer
- type Answers
- type AuthenticationError
- type BadRequestError
- type ChoiceAnswer
- type ChoiceQuestion
- type Client
- type Config
- type InternalServerError
- type ListModelsResponse
- type ModelMetadata
- type ModelsService
- type NotFoundError
- type NoulAnswer
- type NoulCriteria
- type NoulQuestion
- type Option
- func WithAPIKey(apiKey string) Option
- func WithBaseURL(baseURL string) Option
- func WithDefaultModel(model string) Option
- func WithHTTPClient(client *http.Client) Option
- func WithHeader(key, value string) Option
- func WithHeaders(headers map[string]string) Option
- func WithLogger(logger *slog.Logger) Option
- func WithRetryPolicy(policy *RetryPolicy) Option
- func WithTimeout(timeout time.Duration) Option
- func WithTransport(transport http.RoundTripper) Option
- type OverloadedError
- type PermissionDeniedError
- type Question
- type QuestionOption
- type Questions
- type RateLimitError
- type RawQuestion
- type RedactingHandler
- type RequestOption
- func WithCallHeader(key, value string) RequestOption
- func WithCallHeaders(headers map[string]string) RequestOption
- func WithCallRetry(policy *RetryPolicy) RequestOption
- func WithCallTimeout(timeout time.Duration) RequestOption
- func WithExtraBody(extra map[string]any) RequestOption
- func WithModel(model string) RequestOption
- type RetryPolicy
- type SDKError
- type ScoreAnswer
- type ScoreError
- type ScoreQuestion
- type SystemOneResponse
- func (r *SystemOneResponse) Choices() map[string]*ChoiceAnswer
- func (r *SystemOneResponse) MarshalJSON() ([]byte, error)
- func (r *SystemOneResponse) Nouls() map[string]*NoulAnswer
- func (r *SystemOneResponse) RawBody() []byte
- func (r *SystemOneResponse) RawHTTPResponse() (*http.Response, error)
- func (r *SystemOneResponse) RawRequestID() string
- func (r *SystemOneResponse) RequestID() (string, error)
- func (r *SystemOneResponse) Scores() map[string]*ScoreAnswer
- func (r *SystemOneResponse) String() string
- type TypeSafeError
- type UnprocessableEntityError
- type Usage
Examples ¶
Constants ¶
const ( // APIKeyEnv is the environment variable for the TypeSafe API key. APIKeyEnv = "TYPESAFE_API_KEY" // BaseURLEnv is the environment variable for the API base URL. BaseURLEnv = "TYPESAFE_BASE_URL" // DefaultModelEnv is the environment variable for the default model. DefaultModelEnv = "TYPESAFE_DEFAULT_MODEL" // LogLevelEnv is the environment variable for the logging level. LogLevelEnv = "TYPESAFE_LOG_LEVEL" )
Environment variable names.
const ( // DefaultBaseURL is the default API root URL. DefaultBaseURL = "https://api.typesafe.ai" // DefaultModel is the default model name. DefaultModel = "jev-latest" // DefaultTimeout is the default HTTP timeout for operations. DefaultTimeout = 10 * time.Second // DefaultRetryBudget is the default overall retry timeout budget. DefaultRetryBudget = 30 * time.Second // SDKName is the name of this SDK. SDKName = "typesafe-sdk-go" // SDKVersion is the current version of this SDK. SDKVersion = "0.11.0" // MaxErrorBodyLength limits the raw error body length included in error messages. MaxErrorBodyLength = 200 // MaxResponseBodyLimit limits the maximum response body read (16 MB) to prevent OOM DOS attacks. MaxResponseBodyLimit = 16 * 1024 * 1024 )
Client and protocol defaults.
const ( SystemOnePath = "/v1/systemone" ModelsPath = "/v1/models" )
API Endpoint paths.
const ( HeaderAuthorization = "Authorization" HeaderAccept = "Accept" HeaderContentType = "Content-Type" HeaderUserAgent = "User-Agent" HeaderSDK = "X-TypeSafe-SDK" HeaderRuntime = "X-TypeSafe-Runtime" HeaderRetryCount = "X-TypeSafe-Retry-Count" HeaderRequestID = "x-typesafe-request-id" HeaderRetryAfter = "retry-after" HeaderRetryAfterMs = "retry-after-ms" ContentTypeJSON = "application/json" )
Protocol headers.
const (
// StatusOverloaded is the HTTP 529 status code returned when TypeSafe is temporarily overloaded.
StatusOverloaded = 529
)
HTTP Status codes.
Variables ¶
This section is empty.
Functions ¶
func DefaultRetryStatuses ¶
DefaultRetryStatuses returns the set of HTTP status codes the SDK retries by default: 408, 429, and every 5xx.
func NormalizeQuestions ¶
NormalizeQuestions validates the questions before they are encoded and returns the map that is sent on the wire.
It accepts Questions, map[string]Question, map[string]*NoulQuestion and the sibling maps, map[string]any, and RawQuestion values.
Example ¶
normalized, err := NormalizeQuestions(Questions{
"billing": NewNoul("Is this message about billing?"),
"tone": NewChoice(map[string]any{"calm": nil, "angry": nil}),
})
if err != nil {
panic(err)
}
fmt.Println(slices.Sorted(maps.Keys(normalized)))
fmt.Println(normalized["tone"].(Question).QuestionType())
Output: [billing tone] choice
func ParseLogLevel ¶
ParseLogLevel converts a level name into a slog.Level. It accepts "debug", "info", "warn", "warning", "error", and "off", and reports false for any other value.
func RuntimeString ¶
func RuntimeString() string
RuntimeString returns the runtime environment string for SDK identification, sent in the X-TypeSafe-Runtime header.
func SystemOneAs ¶
func SystemOneAs[T any](ctx context.Context, client *Client, state any, questions any, opts ...RequestOption) (*T, error)
SystemOneAs answers questions and decodes the response into T, a caller-defined struct.
T may declare the envelope fields ("model", "usage", "answers") and, additionally, one field per answer, named after the question. Each such field is populated from the matching entry of "answers":
type Ticket struct {
Model string `json:"model"`
Usage typesafe.Usage `json:"usage"`
Billing typesafe.NoulAnswer `json:"billing"`
Tone typesafe.ChoiceAnswer `json:"tone"`
}
result, err := typesafe.SystemOneAs[Ticket](ctx, client, state, questions)
A top-level field that is not a pointer, map, slice, or interface, and that is not tagged `omitempty`, is required: its absence is reported as an APIResponseValidationError naming the field rather than silently decoding to a zero value. Answer types this SDK version does not model are dropped from the decoded document, exactly as in the Client.SystemOne path.
Types ¶
type APIConnectionError ¶
type APIConnectionError struct {
// Message describes the transport failure.
Message string
// Cause is the underlying transport error.
Cause error
}
APIConnectionError is returned when the request never produced an HTTP response.
func (*APIConnectionError) Error ¶
func (e *APIConnectionError) Error() string
func (*APIConnectionError) Unwrap ¶
func (e *APIConnectionError) Unwrap() error
Unwrap returns the underlying transport error.
type APIError ¶
type APIError struct {
// Status is the HTTP response status code.
Status int
// Body is the decoded JSON error body, the raw text for a non-JSON body, or nil for an empty body.
Body any
// RawBody is the exact response body as received.
RawBody []byte
// Headers holds the response headers.
Headers http.Header
// Endpoint is "METHOD URL" with credentials, query parameters, and fragments removed.
// Empty when the response has no originating request.
Endpoint string
// RequestID is the x-typesafe-request-id response header, or empty when absent.
RequestID string
// Message is the human-readable detail shown by Error. The SDK fills it from Body when the
// error is built; a caller constructing an APIError may leave it empty to derive it lazily or
// set it to override the derived text.
Message string
}
APIError is an unsuccessful HTTP response, carrying the full response metadata.
Typed subclasses such as RateLimitError embed APIError and unwrap to it, so a single
var apiErr *typesafe.APIError errors.As(err, &apiErr)
matches every HTTP failure the SDK returns.
func AsAPIError ¶
AsAPIError extracts the APIError carried by any HTTP failure this SDK returns. It reports false for connection, timeout, validation-of-input, and configuration errors.
type APIResponseValidationError ¶
type APIResponseValidationError struct {
APIError
// FieldPath is the dotted path to the offending field, such as "answers.tone.confidence".
FieldPath string
}
APIResponseValidationError is returned for a successful HTTP status whose body does not match the expected schema.
func AsValidationError ¶
func AsValidationError(err error) (*APIResponseValidationError, bool)
AsValidationError extracts the APIResponseValidationError when a successful response could not be decoded.
func (*APIResponseValidationError) Error ¶
func (e *APIResponseValidationError) Error() string
Error returns the formatted validation message.
type APITimeoutError ¶
type APITimeoutError struct {
APIConnectionError
// Timeout is the timeout that was in effect for the request.
Timeout time.Duration
}
APITimeoutError is returned when a request exceeds its configured timeout.
func AsTimeoutError ¶
func AsTimeoutError(err error) (*APITimeoutError, bool)
AsTimeoutError extracts the APITimeoutError when the request exceeded its timeout.
func (*APITimeoutError) Error ¶
func (e *APITimeoutError) Error() string
Error returns the formatted timeout message.
type Answer ¶
type Answer interface {
// AnswerType returns the wire discriminator: "noul", "choice", or "score".
AnswerType() string
}
Answer is a single answer to a single question. The concrete type is NoulAnswer, ChoiceAnswer, or ScoreAnswer, and always matches the corresponding question's type.
type Answers ¶
Answers is a map of question names to answers, decoded by each entry's "type" discriminator.
SystemOneResponse.Answers is a plain map because its entries are already decoded. Answers exists for caller-defined response models passed to SystemOneAs, where Go needs a concrete type to decode into — a bare map[string]Answer cannot be decoded, because an interface type carries no information about which concrete answer to build:
type Analysis struct {
Model string `json:"model"`
Usage typesafe.Usage `json:"usage"`
Answers typesafe.Answers `json:"answers"`
}
func (*Answers) UnmarshalJSON ¶
UnmarshalJSON decodes the answers object, dispatching each entry on its "type" field.
type AuthenticationError ¶
type AuthenticationError struct{ APIError }
AuthenticationError is an HTTP 401 response.
type BadRequestError ¶
type BadRequestError struct{ APIError }
BadRequestError is an HTTP 400 response.
type ChoiceAnswer ¶
type ChoiceAnswer struct {
// Type is always "choice".
Type string `json:"type"`
// Choice is the criteria key with the highest probability.
Choice string `json:"choice"`
// Confidence is the statistical certainty of the choice, from 0 to 1, derived from how the
// probability is concentrated across alternatives. Higher values indicate a clear winner; lower
// values indicate uncertainty or split probabilities. It is the primary signal for confidence-gated
// routing, but where the bands fall is a decision for the caller, because it trades the cost of a
// wrong automatic action against the cost of a human review. The docs gate at 0.5 for a
// high-stakes decision (https://docs.typesafe.ai/confidence) and at 0.6 for a moderation queue
// (https://docs.typesafe.ai/patterns/confidence-routing); treat any number, including 0.5 and
// 0.85, as an example rather than an API-defined threshold.
Confidence float64 `json:"confidence"`
// Probabilities is the probability of each criteria key, from 0 to 1.
Probabilities map[string]float64 `json:"probabilities"`
}
ChoiceAnswer is a selected label with its alternatives.
See the choice primitive (https://docs.typesafe.ai/primitives/choice) for details.
func (*ChoiceAnswer) AnswerType ¶
func (a *ChoiceAnswer) AnswerType() string
AnswerType returns the wire discriminator.
type ChoiceQuestion ¶
type ChoiceQuestion struct {
// Type is always "choice".
Type string `json:"type"`
// Criteria maps each label to its description: a string, a JSON object, an array, or nil for
// an undescribed label. Supports up to 255 options.
Criteria map[string]any `json:"criteria"`
// Instructions is the question to ask, expressed as a string, a JSON object, or an array.
Instructions any `json:"instructions,omitzero"`
// contains filtered or unexported fields
}
ChoiceQuestion selects between named alternatives.
A Choice question picks one option from a defined set of up to 255 alternatives. The response includes the selected option, the full probability distribution, and a confidence score.
See the choice primitive (https://docs.typesafe.ai/primitives/choice) for details.
func NewChoice ¶
func NewChoice(criteria map[string]any, opts ...QuestionOption) *ChoiceQuestion
NewChoice creates a choice question with up to 255 alternative options.
typesafe.NewChoice(
map[string]any{"calm": nil, "angry": "An upset or hostile message"},
typesafe.WithInstructions("What is the tone of this message?"),
)
Example ¶
question := NewChoice(
map[string]any{"calm": nil, "angry": "An upset or hostile message"},
WithInstructions("What is the tone of this message?"),
)
encoded, err := json.Marshal(question)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
Output: {"type":"choice","criteria":{"angry":"An upset or hostile message","calm":null},"instructions":"What is the tone of this message?"}
func (*ChoiceQuestion) MarshalJSON ¶
func (q *ChoiceQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question, including any fields added by WithQuestionField.
func (*ChoiceQuestion) QuestionType ¶
func (q *ChoiceQuestion) QuestionType() string
QuestionType returns the wire discriminator.
func (*ChoiceQuestion) Validate ¶
func (q *ChoiceQuestion) Validate(name string) error
Validate reports a choice question with no criteria at all, with an empty criteria object, or with more than 255 options.
type Client ¶
type Client struct {
// Models provides access to the Models API resource.
Models *ModelsService
// contains filtered or unexported fields
}
Client is the TypeSafe AI API client.
A Client is safe for concurrent use by multiple goroutines: it holds no per-request state, and the underlying http.Client pools connections across calls. Create one Client and share it. All request methods take a context.Context, which is the only cancellation and concurrency control the SDK needs.
func NewClient ¶
NewClient creates a client for the TypeSafe AI API.
Configuration is resolved from the functional options first, then the environment, then the SDK defaults:
client, err := typesafe.NewClient(typesafe.WithTimeout(30*time.Second))
TYPESAFE_API_KEY is required unless WithAPIKey supplies the key.
func (*Client) Close ¶
Close releases the connection pool held by the client's transport.
A transport supplied through WithTransport or WithHTTPClient is closed when it implements io.Closer, so the client owns the resources it was given. Close is idempotent: later calls return the first call's error without touching the transport again.
func (*Client) HTTPClient ¶
HTTPClient returns the underlying *http.Client. A client constructed with WithHTTPClient returns that value; otherwise it returns the SDK's own client.
func (*Client) SystemOne ¶
func (c *Client) SystemOne(ctx context.Context, state any, questions any, opts ...RequestOption) (*SystemOneResponse, error)
SystemOne answers named questions about text or structured state.
See System One (https://docs.typesafe.ai/concepts/system-one) for details.
The response carries every answer keyed by the question name supplied here, alongside the model that answered and the token usage for the evaluation:
response, err := client.SystemOne(ctx,
map[string]any{"message": "I was charged twice. Please help."},
typesafe.Questions{
"billing": typesafe.NewNoul("Is this message about billing?"),
"tone": typesafe.NewChoice(
map[string]any{"calm": nil, "angry": nil},
typesafe.WithInstructions("What is the tone?"),
),
},
)
if err != nil {
return err
}
fmt.Println(response.Nouls()["billing"].Noul)
State can be a string, a JSON-serializable struct, a slice, or a map[string]any. Keep state focused on the relevant context to avoid distractor noise. Questions accepts Questions, map[string]Question, or map[string]any.
Errors are typed: SDKError for invalid input, APIError and its status-specific subclasses (BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, UnprocessableEntityError, RateLimitError, OverloadedError, InternalServerError) for unsuccessful responses, APIConnectionError and APITimeoutError for transport failures, and APIResponseValidationError for a success response that could not be decoded.
type Config ¶
type Config struct {
// APIKey is the credential sent as a bearer token.
APIKey string
// BaseURL is the API root, without a trailing slash.
BaseURL string
// DefaultModel is the model used when a call does not name one.
DefaultModel string
// Timeout is the HTTP timeout for one attempt, in effect for every call.
Timeout time.Duration
// DefaultHeaders holds headers sent with every request. Client identification and credentials
// are applied after these, so a default header cannot displace them.
DefaultHeaders http.Header
// Logger receives the SDK's structured log records.
Logger *slog.Logger
// RetryPolicy is the retry policy in effect for every call.
RetryPolicy *RetryPolicy
// HTTPClient is the caller-supplied HTTP client, or nil when the SDK owns its client.
HTTPClient *http.Client
// Transport is the caller-supplied transport, or nil when the SDK owns its transport.
Transport http.RoundTripper
}
Config is the resolved client configuration.
type InternalServerError ¶
type InternalServerError struct{ APIError }
InternalServerError is an HTTP 5xx response.
type ListModelsResponse ¶
type ListModelsResponse struct {
// Models holds the available models.
Models []ModelMetadata `json:"models"`
// contains filtered or unexported fields
}
ListModelsResponse holds the models available to the account.
func (*ListModelsResponse) MarshalJSON ¶
func (r *ListModelsResponse) MarshalJSON() ([]byte, error)
MarshalJSON encodes only the API payload, leaving HTTP metadata out of the result.
func (*ListModelsResponse) RawBody ¶
func (r *ListModelsResponse) RawBody() []byte
RawBody returns the response payload exactly as received.
func (*ListModelsResponse) RawHTTPResponse ¶
func (r *ListModelsResponse) RawHTTPResponse() (*http.Response, error)
RawHTTPResponse returns the underlying *http.Response.
func (*ListModelsResponse) RawRequestID ¶
func (r *ListModelsResponse) RawRequestID() string
RawRequestID returns the request ID, or the empty string when absent.
func (*ListModelsResponse) RequestID ¶
func (r *ListModelsResponse) RequestID() (string, error)
RequestID returns the x-typesafe-request-id response header. It reports an error when the response did not carry one.
type ModelMetadata ¶
type ModelMetadata struct {
// Name is the model name or alias accepted by a request's model field.
Name string `json:"name"`
// Description describes the model and its capabilities.
Description string `json:"description"`
// ReleaseDate is the model's release date as the API reports it. The published schema
// describes a YYYY-MM-DD date; the live API currently returns a full timestamp.
ReleaseDate string `json:"release_date"`
}
ModelMetadata describes a model available to the account.
type ModelsService ¶
type ModelsService struct {
// contains filtered or unexported fields
}
ModelsService exposes the Models API resource. Reach it through Client.Models.
func (*ModelsService) List ¶
func (s *ModelsService) List(ctx context.Context, opts ...RequestOption) (*ListModelsResponse, error)
List returns the models available to the account, with each model's name, description, and release date.
response, err := client.Models.List(ctx)
for _, model := range response.Models {
fmt.Println(model.Name, model.ReleaseDate)
}
type NoulAnswer ¶
type NoulAnswer struct {
// Type is always "noul".
Type string `json:"type"`
// Noul is the probability of a yes answer or a true statement, from 0 to 1.
Noul float64 `json:"noul"`
}
NoulAnswer is a yes/no answer.
See the noul primitive (https://docs.typesafe.ai/primitives/noul) for details.
func (*NoulAnswer) AnswerType ¶
func (a *NoulAnswer) AnswerType() string
AnswerType returns the wire discriminator.
type NoulCriteria ¶
type NoulCriteria struct {
// True describes what counts as a yes answer.
True any `json:"true,omitzero"`
// False describes what counts as a no answer.
False any `json:"false,omitzero"`
}
NoulCriteria describes what counts as a yes or no answer. Both fields accept a string, a map, or a slice, and may be left nil.
type NoulQuestion ¶
type NoulQuestion struct {
// Type is always "noul".
Type string `json:"type"`
// Instructions is the question to ask, expressed as a string, a JSON object, or an array.
Instructions any `json:"instructions,omitzero"`
// Criteria describes what counts as a yes or no answer.
Criteria *NoulCriteria `json:"criteria,omitzero"`
// contains filtered or unexported fields
}
NoulQuestion is a yes/no question or statement.
See the noul primitive (https://docs.typesafe.ai/primitives/noul) for details.
func NewNoul ¶
func NewNoul(instructions any, opts ...QuestionOption) *NoulQuestion
NewNoul creates a yes/no question. The instructions may be a string, a map, or a slice, and may be nil when criteria alone describe the question; the request schema requires neither field.
typesafe.NewNoul("Is this message spam?")
typesafe.NewNoul("Is this spam?", typesafe.WithNoulCriteria(typesafe.NoulCriteria{
True: "Unsolicited advertising",
False: "A legitimate conversation",
}))
Options refine the question: WithInstructions overrides the instructions given positionally.
Example ¶
question := NewNoul("Is this message about billing?")
fmt.Println(question.QuestionType())
fmt.Println(question.Instructions)
Output: noul Is this message about billing?
func (*NoulQuestion) MarshalJSON ¶
func (q *NoulQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question, including any fields added by WithQuestionField.
func (*NoulQuestion) QuestionType ¶
func (q *NoulQuestion) QuestionType() string
QuestionType returns the wire discriminator.
func (*NoulQuestion) Validate ¶
func (q *NoulQuestion) Validate(string) error
Validate accepts every noul question. The request schema requires no field for a noul, so a bare statement, a statement with criteria, and criteria alone are all well-formed requests.
type Option ¶
type Option func(*configOptions)
Option configures a Client at construction time.
func WithAPIKey ¶
WithAPIKey sets the API key, overriding the TYPESAFE_API_KEY environment variable.
func WithBaseURL ¶
WithBaseURL sets the API root, overriding the TYPESAFE_BASE_URL environment variable.
func WithDefaultModel ¶
WithDefaultModel sets the model used when a call does not name one, overriding the TYPESAFE_DEFAULT_MODEL environment variable.
func WithHTTPClient ¶
WithHTTPClient supplies the http.Client the SDK sends through, together with its transport. Mutually exclusive with WithTransport.
func WithHeader ¶
WithHeader sets a default header sent with every request. Client identification and credentials are applied afterwards and cannot be displaced.
func WithHeaders ¶
WithHeaders sets default headers sent with every request, merging with any set by WithHeader.
func WithLogger ¶
WithLogger sets the structured logger. The SDK logs requests, responses, and retries at info level, and headers and bodies at debug level, with credential headers redacted.
func WithRetryPolicy ¶
func WithRetryPolicy(policy *RetryPolicy) Option
WithRetryPolicy sets the retry policy for every call.
func WithTimeout ¶
WithTimeout sets the HTTP timeout for one attempt. It must be positive.
The timeout has no environment variable. When WithHTTPClient supplies both a client and no explicit timeout, the client's own Timeout is adopted instead.
func WithTransport ¶
func WithTransport(transport http.RoundTripper) Option
WithTransport supplies the http.RoundTripper the SDK sends through. Mutually exclusive with WithHTTPClient.
type OverloadedError ¶ added in v0.10.1
type OverloadedError struct {
APIError
// RetryAfterMS is the wait the server requested in milliseconds, or nil when the
// response carried no usable Retry-After or retry-after-ms header.
RetryAfterMS *float64
}
OverloadedError is an HTTP 529 response indicating that TypeSafe is temporarily overloaded.
func AsOverloadedError ¶ added in v0.10.1
func AsOverloadedError(err error) (*OverloadedError, bool)
AsOverloadedError extracts the OverloadedError when the failure was an HTTP 529.
func (*OverloadedError) RetryAfter ¶ added in v0.10.1
func (e *OverloadedError) RetryAfter() (time.Duration, bool)
RetryAfter returns the server-requested wait and whether the response provided one.
type PermissionDeniedError ¶
type PermissionDeniedError struct{ APIError }
PermissionDeniedError is an HTTP 403 response.
type Question ¶
type Question interface {
// QuestionType returns the wire discriminator: "noul", "choice", or "score".
QuestionType() string
// Validate reports whether the question is well formed under the given name.
Validate(name string) error
}
Question is implemented by every question the SDK can send: NoulQuestion, ChoiceQuestion, ScoreQuestion, and RawQuestion.
type QuestionOption ¶
type QuestionOption func(*questionOptions)
QuestionOption refines a question constructed by NewNoul, NewChoice, or NewScore.
func WithInstructions ¶
func WithInstructions(instructions any) QuestionOption
WithInstructions sets the question's instructions: the question to ask, expressed as a string, a JSON object, or an array.
func WithNoulCriteria ¶
func WithNoulCriteria(criteria NoulCriteria) QuestionOption
WithNoulCriteria sets the criteria describing the yes and no outcomes of a noul question.
Example ¶
question := NewNoul("Is this spam?", WithNoulCriteria(NoulCriteria{
True: "Unsolicited advertising",
False: "A legitimate conversation",
}))
encoded, err := json.Marshal(question)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
Output: {"type":"noul","instructions":"Is this spam?","criteria":{"true":"Unsolicited advertising","false":"A legitimate conversation"}}
func WithQuestionField ¶
func WithQuestionField(key string, value any) QuestionOption
WithQuestionField adds a field to a question's wire form. It exists for forward compatibility with request fields this SDK version does not model; a field that collides with a modeled one replaces it.
Example ¶
question := NewNoul("Is this message about billing?", WithQuestionField("language", "en"))
encoded, err := json.Marshal(question)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
Output: {"instructions":"Is this message about billing?","language":"en","type":"noul"}
type Questions ¶
Questions maps the names you choose to the questions they identify. The names key the answers in the response.
The values are typed as Question, so a value that is not a question is a compile error. Build them with NewNoul, NewChoice, and NewScore, or with RawQuestion for a question shape this version does not model. To pass a plain map[string]any, hand it to the client directly — the question parameter accepts any accepted container, which NormalizeQuestions validates.
type RateLimitError ¶
type RateLimitError struct {
APIError
// RetryAfterMS is the wait the server requested in milliseconds, or nil when the
// response carried no usable Retry-After or retry-after-ms header.
RetryAfterMS *float64
}
RateLimitError is an HTTP 429 response.
func AsRateLimitError ¶
func AsRateLimitError(err error) (*RateLimitError, bool)
AsRateLimitError extracts the RateLimitError when the failure was an HTTP 429. If the failure was an HTTP 529 OverloadedError, it produces a compatible RateLimitError so rate-limiting and overload backoff logic can treat them interchangeably.
func (*RateLimitError) RetryAfter ¶
func (e *RateLimitError) RetryAfter() (time.Duration, bool)
RetryAfter returns the server-requested wait and whether the response provided one.
type RawQuestion ¶
RawQuestion is a question sent as a plain map, for request fields this SDK version does not model:
typesafe.RawQuestion{"type": "noul", "instructions": "Is this about billing?"}
func (RawQuestion) QuestionType ¶
func (q RawQuestion) QuestionType() string
QuestionType returns the "type" field, or the empty string when absent.
func (RawQuestion) Validate ¶
func (q RawQuestion) Validate(name string) error
Validate applies the structural checks the SDK can make without duplicating the server's schema: the type must be a non-empty string, and a choice or score question must carry criteria. A choice question must also declare a countable number of options, between one and 255.
type RedactingHandler ¶
type RedactingHandler struct {
// contains filtered or unexported fields
}
RedactingHandler wraps an slog.Handler and redacts credential-bearing attributes before they reach it.
The SDK already redacts the headers it logs itself; this handler extends that guarantee to records the caller routes through the SDK's logger:
logger := slog.New(typesafe.NewRedactingHandler(slog.NewJSONHandler(os.Stdout, nil))) client, err := typesafe.NewClient(typesafe.WithLogger(logger))
Values held in a map[string]string under any attribute are inspected key by key.
func NewRedactingHandler ¶
func NewRedactingHandler(handler slog.Handler) *RedactingHandler
NewRedactingHandler wraps handler so that credential-bearing attributes are redacted.
func (*RedactingHandler) Handle ¶
Handle redacts the record's attributes and forwards it to the wrapped handler. Attributes that carry no credential are passed through unchanged.
type RequestOption ¶
type RequestOption func(*requestOptions)
RequestOption overrides client configuration for a single call.
func WithCallHeader ¶
func WithCallHeader(key, value string) RequestOption
WithCallHeader sets or overrides a request header for a single call.
func WithCallHeaders ¶
func WithCallHeaders(headers map[string]string) RequestOption
WithCallHeaders sets or overrides request headers for a single call, merging with any set by WithCallHeader.
func WithCallRetry ¶
func WithCallRetry(policy *RetryPolicy) RequestOption
WithCallRetry overrides the retry policy for a single call.
func WithCallTimeout ¶
func WithCallTimeout(timeout time.Duration) RequestOption
WithCallTimeout overrides the HTTP timeout for a single call. It must be positive.
func WithExtraBody ¶
func WithExtraBody(extra map[string]any) RequestOption
WithExtraBody adds top-level fields to a request body, shallow-merged over "state", "model", and "questions" after they are set. Merging is last-write-wins: a colliding key replaces the SDK's value, and object values are replaced rather than deep-merged.
func WithModel ¶
func WithModel(model string) RequestOption
WithModel overrides the model for a single call.
type RetryPolicy ¶
type RetryPolicy struct {
// MaxRetries is the number of retries after the initial attempt. Zero disables retries.
MaxRetries int
// BackoffInitial is the first backoff delay; it doubles per attempt up to BackoffMax.
// Zero disables backoff.
BackoffInitial time.Duration
// BackoffMax caps the backoff delay. Zero disables backoff.
BackoffMax time.Duration
// BackoffJitter is the fraction of each backoff delay that is randomly subtracted,
// between zero and one.
BackoffJitter float64
// HTTPStatuses holds the response status codes that are retried.
HTTPStatuses map[int]bool
// RespectRetryAfter honors the retry-after-ms and Retry-After response headers.
RespectRetryAfter bool
// APIConnectionError retries [APIConnectionError] failures.
APIConnectionError bool
// APITimeoutError retries [APITimeoutError] failures.
APITimeoutError bool
// RetryErrors holds additional error values that trigger a retry. A failure is retried when
// errors.Is reports a match against any entry.
RetryErrors []error
// Predicate is consulted for every failure. Returning true triggers a retry, in addition to
// the rules above.
Predicate func(error) bool
// Timeout is the total budget for one SDK call, covering the initial attempt, retries, and
// delays. A retry whose delay would reach or exceed the remaining budget is abandoned and the
// last error is returned. Nil disables the budget.
Timeout *time.Duration
// Sleep waits between attempts. It is a seam for tests; nil uses a context-aware timer.
Sleep func(ctx context.Context, delay time.Duration) error
// RandFloat returns a value in [0, 1) used for backoff jitter. It is a seam for tests;
// nil uses the global math/rand/v2 source.
RandFloat func() float64
}
RetryPolicy configures how the client retries failed requests.
The zero value is not usable; start from DefaultRetryPolicy and adjust, or set the fields you care about and leave the rest zero:
policy := typesafe.DefaultRetryPolicy() policy.MaxRetries = 3 policy.Timeout = nil // no overall budget
A non-nil policy passed to WithRetryPolicy or WithCallRetry is validated when it is used.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy() *RetryPolicy
DefaultRetryPolicy returns a fresh copy of the retry policy the SDK uses when none is supplied.
func (*RetryPolicy) Clone ¶
func (p *RetryPolicy) Clone() *RetryPolicy
Clone returns a deep copy of the policy, so a per-call override can be adjusted without mutating the client's policy.
func (*RetryPolicy) Execute ¶
Execute runs fn under this policy, retrying per the policy's rules.
fn receives the zero-based attempt index. Execute stops early, returning the last failure, when the retry budget would be exceeded, when the failure is not retryable, or when the caller's context ends. A cancelled or expired context is never retried.
func (*RetryPolicy) Validate ¶
func (p *RetryPolicy) Validate() error
Validate reports whether the policy is well formed.
type SDKError ¶
type SDKError struct {
// Message describes the failure.
Message string
// Cause is the wrapped error, when one exists.
Cause error
}
SDKError is a client-side failure: invalid configuration, an unencodable body, or a response that could not be read. It never represents an unsuccessful HTTP status.
type ScoreAnswer ¶
type ScoreAnswer struct {
// Type is always "score".
Type string `json:"type"`
// Score is the probability-weighted average across discrete rubric levels (0 to len(Legend)-1);
// it may fall between levels. Suitable for threshold comparison, not for continuous magnitude reconstruction.
Score float64 `json:"score"`
// Confidence is the confidence in the rating, from 0 to 1.
Confidence float64 `json:"confidence"`
// Legend maps each rubric level to the criteria description supplied in the request. Keys are the
// level numbers as the API sends them — strings — and the values stay untyped because a level
// description may be a string, an object, or an array. [ScoreAnswer.LegendFor] reads a level by
// number, the way Probabilities is keyed.
Legend map[string]any `json:"legend"`
// Probabilities is the probability of each rubric level, keyed by the integer level number.
Probabilities map[int]float64 `json:"probabilities"`
}
ScoreAnswer is a rating against an ordered rubric.
Model Behavior Note (Jev 1.13): Score is the probability-weighted average of discrete rubric levels. It is calibrated for threshold gating and ordinal classification (e.g. score >= 1.5). Do NOT use Score as a continuous linear interpolator or calculator to reconstruct exact numerical magnitudes between levels, because System One models are qualitative decision engines, not numeric calculators.
See the score primitive (https://docs.typesafe.ai/primitives/score) and model jaggedness (https://docs.typesafe.ai/model-jaggedness/jev-1.13) for details.
func (*ScoreAnswer) AnswerType ¶
func (a *ScoreAnswer) AnswerType() string
AnswerType returns the wire discriminator.
func (*ScoreAnswer) LegendFor ¶ added in v0.11.0
func (a *ScoreAnswer) LegendFor(level int) (any, bool)
LegendFor returns the legend entry for a rubric level number, the level Probabilities is keyed by. It reports false when the level is absent from the legend.
func (*ScoreAnswer) MarshalJSON ¶
func (a *ScoreAnswer) MarshalJSON() ([]byte, error)
MarshalJSON encodes the rubric probabilities with their integer levels as JSON object keys.
type ScoreError ¶
type ScoreError struct {
// Name is the question name that failed validation.
Name string
}
ScoreError reports a score question with an invalid rubric.
func (*ScoreError) Error ¶
func (e *ScoreError) Error() string
type ScoreQuestion ¶
type ScoreQuestion struct {
// Type is always "score".
Type string `json:"type"`
// Criteria is the ordered rubric: each entry's position is its score, starting at zero. Each
// entry is a string, a JSON object, or an array. Must contain between 2 and 10 levels.
Criteria []any `json:"criteria"`
// Instructions is the question to ask, expressed as a string, a JSON object, or an array.
Instructions any `json:"instructions,omitzero"`
// contains filtered or unexported fields
}
ScoreQuestion rates content using an ordered rubric.
A Score rubric requires between 2 and 10 levels. The response includes an expected score (probability-weighted average), a probability for each level, and a confidence score. Use Score for threshold gating rather than continuous numeric calculations.
See the score primitive (https://docs.typesafe.ai/primitives/score) for details.
func NewScore ¶
func NewScore(criteria []any, opts ...QuestionOption) *ScoreQuestion
NewScore creates a score question with an ordered rubric containing between 2 and 10 levels.
typesafe.NewScore(
[]any{"can wait", "needs attention this week", "needs attention today"},
typesafe.WithInstructions("How urgent is this message?"),
)
Example ¶
question := NewScore(
[]any{"can wait", "needs attention this week", "needs attention today"},
WithInstructions("How urgent is this message?"),
)
encoded, err := json.Marshal(question)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
Output: {"type":"score","criteria":["can wait","needs attention this week","needs attention today"],"instructions":"How urgent is this message?"}
func (*ScoreQuestion) MarshalJSON ¶
func (q *ScoreQuestion) MarshalJSON() ([]byte, error)
MarshalJSON encodes the question, including any fields added by WithQuestionField.
func (*ScoreQuestion) QuestionType ¶
func (q *ScoreQuestion) QuestionType() string
QuestionType returns the wire discriminator.
func (*ScoreQuestion) Validate ¶
func (q *ScoreQuestion) Validate(name string) error
Validate reports a score question whose rubric does not have between 2 and 10 levels.
type SystemOneResponse ¶
type SystemOneResponse struct {
// Model is the name of the model that answered the questions; it may differ from the alias
// supplied in the request.
Model string `json:"model"`
// Usage reports the token counts for this evaluation.
Usage Usage `json:"usage"`
// Answers holds every answer keyed by the question name supplied in the request. Answer types
// this SDK version does not model are omitted here and remain reachable through RawBody.
Answers map[string]Answer `json:"answers"`
// contains filtered or unexported fields
}
SystemOneResponse holds the answers to a System One request, grouped by question kind.
See System One (https://docs.typesafe.ai/concepts/system-one) for details.
func (*SystemOneResponse) Choices ¶
func (r *SystemOneResponse) Choices() map[string]*ChoiceAnswer
Choices returns the choice answers keyed by question name.
func (*SystemOneResponse) MarshalJSON ¶
func (r *SystemOneResponse) MarshalJSON() ([]byte, error)
MarshalJSON encodes only the API payload, leaving HTTP metadata out of the result.
func (*SystemOneResponse) Nouls ¶
func (r *SystemOneResponse) Nouls() map[string]*NoulAnswer
Nouls returns the yes/no answers keyed by question name.
func (*SystemOneResponse) RawBody ¶
func (r *SystemOneResponse) RawBody() []byte
RawBody returns the response payload exactly as received, including any answer types this SDK version does not model.
func (*SystemOneResponse) RawHTTPResponse ¶
func (r *SystemOneResponse) RawHTTPResponse() (*http.Response, error)
RawHTTPResponse returns the underlying *http.Response.
func (*SystemOneResponse) RawRequestID ¶
func (r *SystemOneResponse) RawRequestID() string
RawRequestID returns the request ID, or the empty string when absent.
func (*SystemOneResponse) RequestID ¶
func (r *SystemOneResponse) RequestID() (string, error)
RequestID returns the x-typesafe-request-id response header. It reports an error when the response did not carry one.
func (*SystemOneResponse) Scores ¶
func (r *SystemOneResponse) Scores() map[string]*ScoreAnswer
Scores returns the score answers keyed by question name.
func (*SystemOneResponse) String ¶
func (r *SystemOneResponse) String() string
String implements fmt.Stringer for convenient debugging of a response.
type TypeSafeError ¶
type TypeSafeError interface {
error
// contains filtered or unexported methods
}
TypeSafeError is the marker interface implemented by every error this SDK returns.
Match it with errors.As:
if err != nil {
var sdkErr typesafe.TypeSafeError
if errors.As(err, &sdkErr) {
// the failure came from the SDK
}
}
type UnprocessableEntityError ¶
type UnprocessableEntityError struct{ APIError }
UnprocessableEntityError is an HTTP 422 response.
type Usage ¶
type Usage struct {
// InputTokens is the number of billable input tokens, or nil when the API did not report it.
InputTokens *int `json:"input_tokens,omitzero"`
// OutputTokens is the number of output tokens, or nil when the API did not report it.
OutputTokens *int `json:"output_tokens,omitzero"`
}
Usage reports the token counts for a request.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
typesafe
command
Command typesafe is a command-line client for the TypeSafe AI API.
|
Command typesafe is a command-line client for the TypeSafe AI API. |
|
examples
|
|
|
advanced
command
Command advanced exercises the parts of the TypeSafe AI Go SDK a production caller reaches for: per-call overrides, a custom retry policy, a typed response model, a second API resource, and the typed error taxonomy.
|
Command advanced exercises the parts of the TypeSafe AI Go SDK a production caller reaches for: per-call overrides, a custom retry policy, a typed response model, a second API resource, and the typed error taxonomy. |
|
quickstart
command
Command quickstart is the smallest useful TypeSafe AI Go SDK program: one System One request that answers a noul, a choice, and a score question about a structured state.
|
Command quickstart is the smallest useful TypeSafe AI Go SDK program: one System One request that answers a noul, a choice, and a score question about a structured state. |