Skip to content

Repository files navigation

typesafe-go

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)    // billing

Install

go get github.com/lib-x/typesafe-go

Requires Go 1.23 or newer. The SDK depends only on the standard library.

Authentication

Set TYPESAFE_API_KEY in the environment, or pass APIKey to typesafe.Config:

export TYPESAFE_API_KEY=ts_...

Question types

Every question is keyed by the name you choose; answers come back under the same name.

Noul — a yes/no question

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",
})

Choice — pick one option

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
})

Score — rate against a rubric

Returns a probability-weighted score that may fall between levels.

typesafe.Score("How frustrated is the customer?", []any{
    "Calm", "Frustrated", "Very angry",
})

Structured questions

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",
    },
})

Raw questions

Send a question mapping built elsewhere; SystemOne validates its shape before sending.

typesafe.RawQuestion{
    "type":         "noul",
    "instructions": "Is this spam?",
}

Reading answers

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.ScoreAnswer

Answers 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.

Errors

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.

Retries

Requests retry on 408, 429, 500599, 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})

Configuration

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

models, err := client.Models(ctx)
for _, model := range models {
    fmt.Println(model.Name, model.Description, model.ReleaseDate)
}

Logging

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.

Concurrency

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.

License

MIT — see LICENSE.

About

Go SDK for the TypeSafe AI API: typed questions in, structured answers out. Zero dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages