Go client for the TypeSafe AI API.
Community-maintained SDK. Not affiliated with or endorsed by TypeSafe AI. See the official SDKs for JavaScript and Python.
TypeSafe answers typed questions about text or structured state. You describe the questions, it returns structured answers — probabilities, choices, and scores — instead of free-form text you have to parse.
client, err := typesafe.NewClient(typesafe.Config{})
if err != nil {
log.Fatal(err)
}
response, err := client.SystemOne(ctx, typesafe.SystemOneRequest{
State: "Help! My payouts have been failing for 3 days.",
Questions: map[string]typesafe.Question{
"is_urgent": typesafe.Noul("Does this convey urgency?"),
"department": typesafe.Choice("Which team should handle this?", map[string]any{
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
}),
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response.Nouls()["is_urgent"].Noul) // 0.95
fmt.Println(response.Choices()["department"].Choice) // billinggo get github.com/lib-x/typesafe-goRequires Go 1.23 or newer. The SDK depends only on the standard library.
Set TYPESAFE_API_KEY in the environment, or pass APIKey to typesafe.Config:
export TYPESAFE_API_KEY=ts_...Every question is keyed by the name you choose; answers come back under the same name.
Returns the probability that the answer is yes.
typesafe.Noul("Does this message contain unsolicited advertising?")
// With descriptions of what each outcome means.
typesafe.NoulWithCriteria("Is this spam?", typesafe.NoulCriteria{
True: "Unsolicited advertising",
False: "A legitimate conversation",
})Returns the most likely option, its confidence, and the full distribution.
typesafe.Choice("Which team should handle this?", map[string]any{
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": nil, // described by its name alone
})Returns a probability-weighted score that may fall between levels.
typesafe.Score("How frustrated is the customer?", []any{
"Calm", "Frustrated", "Very angry",
})state, instructions, and criteria entries accept any JSON value, so a
question can carry the data it refers to:
typesafe.Noul(map[string]any{
"question": "Is the resume for the same person as `candidate`?",
"candidate": map[string]any{
"name": "John Smith",
"location": "Oakland, California",
},
})Send a question mapping built elsewhere; SystemOne validates its shape before
sending.
typesafe.RawQuestion{
"type": "noul",
"instructions": "Is this spam?",
}Answers holds every answer keyed by question name. The typed helpers return the
subsets you care about:
response.Answers // map[string]typesafe.Answer
response.Nouls() // map[string]typesafe.NoulAnswer
response.Choices() // map[string]typesafe.ChoiceAnswer
response.Scores() // map[string]typesafe.ScoreAnswerAnswers of a type this SDK version does not model arrive as typesafe.UnknownAnswer
with the raw payload preserved, so a new API answer type never breaks an existing
program.
response.Usage reports token counts, and response.Raw holds the complete
response body.
All failures are typed, so errors.As works as usual:
response, err := client.SystemOne(ctx, request)
if err != nil {
var apiErr *typesafe.APIError
if errors.As(err, &apiErr) {
switch {
case apiErr.IsAuthentication():
log.Fatal("check TYPESAFE_API_KEY")
case apiErr.IsRateLimit():
log.Printf("rate limited; retry after %s", apiErr.RetryAfter())
case apiErr.IsUnprocessableEntity():
log.Fatalf("the API rejected the request: %s", apiErr.Message)
}
log.Printf("request id: %s", apiErr.RequestID)
return err
}
var timeoutErr *typesafe.APITimeoutError
if errors.As(err, &timeoutErr) {
log.Printf("timed out after %s", timeoutErr.Timeout)
}
return err
}| Error | Meaning |
|---|---|
*typesafe.APIError |
The API returned a non-2xx response. Carries StatusCode, Body, Headers, RequestID, Message, and Endpoint. |
*typesafe.APIConnectionError |
The request failed before receiving a response (DNS, TLS, connection reset). |
*typesafe.APITimeoutError |
The attempt exceeded its timeout. Unwraps to context.DeadlineExceeded. |
*typesafe.UserAbortError |
The caller cancelled through its context. Unwraps to context.Canceled. |
*typesafe.APIResponseError |
A 2xx response whose body did not match the expected structure. |
*typesafe.Error |
Invalid configuration or a malformed request built by the SDK. |
Requests retry on 408, 429, 500–599, connection failures, and timeouts,
with exponential backoff, jitter, and Retry-After support. The defaults are:
| Setting | Default |
|---|---|
MaxRetries |
2 |
BackoffInitial |
500ms |
BackoffMax |
5s |
BackoffJitter |
0.25 |
HTTPStatuses |
408, 429, 500–599 |
RespectRetryAfter |
true |
MaxRetryAfter |
60s |
RetryConnectionErrors |
true |
RetryTimeoutErrors |
true |
Start from typesafe.DefaultRetryPolicy() and change what you need:
policy := typesafe.DefaultRetryPolicy()
policy.MaxRetries = 5
policy.HTTPStatuses = map[int]bool{429: true, 503: true}
client, err := typesafe.NewClient(typesafe.Config{Retry: &policy})Disable retries entirely with the zero policy:
noRetry := typesafe.RetryPolicy{}
client, err := typesafe.NewClient(typesafe.Config{Retry: &noRetry})client, err := typesafe.NewClient(typesafe.Config{
APIKey: "ts_...", // TYPESAFE_API_KEY
BaseURL: "https://api.typesafe.ai", // TYPESAFE_BASE_URL
DefaultModel: "jev-latest", // TYPESAFE_DEFAULT_MODEL
Timeout: 10 * time.Second,
Retry: &policy,
Headers: map[string]string{"X-Tenant": "acme"},
LogLevel: typesafe.LogLevelInfo, // TYPESAFE_LOG_LEVEL
HTTPClient: customClient,
Logger: customLogger,
})Explicit fields win over environment variables, which win over SDK defaults. Empty or whitespace-only environment values are ignored.
Per call, override what you need:
response, err := client.SystemOne(ctx, request,
typesafe.WithModel("jev-latest"),
typesafe.WithTimeout(30*time.Second),
typesafe.WithRetry(policy),
typesafe.WithHeaders(map[string]string{"X-Trace-Id": traceID}),
typesafe.WithExtraBody(map[string]any{"temperature": 0.2}),
)models, err := client.Models(ctx)
for _, model := range models {
fmt.Println(model.Name, model.Description, model.ReleaseDate)
}LogLevel accepts debug, info, warn, error, and off (default warn).
debug logs request headers, request bodies, and response bodies; credential
headers (Authorization, X-Api-Key, Cookie, and similar) are redacted before
they reach the logger. Implement typesafe.Logger to route logs elsewhere.
A Client is safe for concurrent use. SystemOne and Models take a
context.Context for cancellation and deadlines, and every attempt carries its
own timeout.
MIT — see LICENSE.