typesafe

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 12 Imported by: 0

README

typesafe-go

Go Reference CI Go Report Card License

An unofficial, community-built Go client for the TypeSafe AI System One API.

[!IMPORTANT] This is not an official TypeSafe AI product. It is an independent, community-built SDK maintained by @sachin-handiekar. It is not affiliated with, endorsed by, sponsored by, or supported by TypeSafe AI.

  • Bugs and feature requests for this library belong on its issue tracker. Please do not raise them with TypeSafe support.
  • Questions about the API itself — endpoints, model behavior, pricing, quotas — belong with TypeSafe at docs.typesafe.ai.
  • "TypeSafe", "TypeSafe AI", and "System One" are the marks of their respective owners, used here only to describe what this client connects to.

System One models make fast, structured decisions for software. You send state and typed questions; you get structured answers your code can use directly — no prompt parsing, no JSON coaxing, no string matching on model output.

  • Typed answers. Every question has a declared type, and every answer decodes into a Go struct.
  • No dependencies. Standard library only.
  • Production defaults. Timeouts, exponential backoff with jitter, Retry-After support, and request IDs on every response.

Install

go get github.com/sachin-handiekar/typesafe-go

Requires Go 1.22 or later.

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

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: "Help! My payouts have been failing for 3 days.",
		Questions: typesafe.Questions{
			"is_urgent": typesafe.Noul("Does this convey urgency?"),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	urgent, err := resp.Answers["is_urgent"].AsNoul()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Urgent: %.0f%%\n", urgent.Value*100) // Urgent: 95%
}

Authentication

Credentials are resolved in order:

  1. The explicit WithAPIKey option
  2. The TYPESAFE_API_KEY environment variable
client, err := typesafe.NewClient(typesafe.WithAPIKey(os.Getenv("MY_KEY")))

NewClient returns an error rather than failing later if no key is available, or if the key contains whitespace or non-printable characters.

The three question types

Questions are a map you name yourself. Answers come back under the same keys.

Noul — yes/no as a probability
Questions: typesafe.Questions{
	"is_urgent": typesafe.Noul("Does this convey urgency?"),
}

AsNoul() returns a NoulAnswer whose Value runs from 0 (no) to 1 (yes). When "yes" and "no" are ambiguous, spell them out:

"is_spam": typesafe.NoulWithCriteria("Is this spam?", typesafe.NoulCriteria{
	True:  "unsolicited bulk promotion",
	False: "a real message from a real person",
}),
Choice — pick one option from a set
"topic": typesafe.Choice("What is this about?", map[string]any{
	"billing":   "payments, invoices, refunds",
	"technical": "bugs, outages, integration problems",
	"other":     nil, // nil when an option needs no extra detail
}),

AsChoice() returns the winning Choice, the full Probabilities map, and a Confidence derived from those probabilities. Maximum 255 options.

Score — rate along an ordered rubric
"severity": typesafe.Score("How severe is this?", []any{
	"cosmetic, no user impact",
	"degraded but usable",
	"fully broken, revenue affected",
}),

AsScore() returns a probability-weighted Score that can land between levels, plus the Legend, Probabilities, and Confidence. Between 2 and 10 levels.

Reading many answers at once

When you ask several questions of the same type, the filter helpers avoid a type switch per answer:

for name, n := range resp.Nouls() {
	fmt.Printf("%s: %.2f\n", name, n.Value)
}

Nouls(), Choices(), and Scores() each return only the answers of that type, keyed by question name.

Structured state

State is not limited to a string. Pass any JSON-encodable value — a chat log, a database record, or your application's state:

resp, err := client.SystemOne(ctx, &typesafe.SystemOneRequest{
	State: []map[string]string{
		{"role": "customer", "text": "Where is my refund?"},
		{"role": "agent", "text": "Let me check on that."},
	},
	Questions: typesafe.Questions{
		"needs_escalation": typesafe.Noul("Should a human take over?"),
	},
})

Choosing a model

If Model is empty, the client's default is used (jev-latest, overridable with WithDefaultModel or TYPESAFE_DEFAULT_MODEL).

resp, err := client.SystemOne(ctx, &typesafe.SystemOneRequest{
	Model:     typesafe.ModelJevPreview,
	State:     "...",
	Questions: questions,
})
Constant Value Points at
ModelJevLatest jev-latest Most recent stable, official Jev release
ModelJevPreview jev-preview Most recent release, official or not
ModelJev1_13_0 jev-1.13.0 A pinned version

To list what your account can reach:

models, err := client.Models.List(ctx)
for _, m := range models.Models {
	fmt.Printf("%s (%s): %s\n", m.Name, m.ReleaseDate, m.Description)
}

Errors

Non-2xx responses return an *APIError carrying the status code, the message extracted from the body, and the request ID to quote in a support thread:

resp, err := client.SystemOne(ctx, req)
if err != nil {
	var apiErr *typesafe.APIError
	if errors.As(err, &apiErr) {
		log.Printf("status=%d request_id=%s: %s",
			apiErr.StatusCode, apiErr.RequestID, apiErr.Message)
	}
	return err
}

Sentinel errors work with errors.Is, and the common cases have helpers:

switch {
case typesafe.IsRateLimited(err):     // 429
case typesafe.IsOverloaded(err):      // 529
case typesafe.IsUnauthorized(err):    // 401
case typesafe.IsNotFound(err):        // 404
case typesafe.IsUnprocessableEntity(err): // 422
}

The full set: ErrBadRequest, ErrUnauthorized, ErrForbidden, ErrNotFound, ErrUnprocessableEntity, ErrRateLimited, ErrOverloaded, ErrInternalServer, and ErrResponseTooLarge (a successful response body above the SDK's 1 MiB read limit, which is never retried).

Every successful response also carries RequestID, taken from the x-typesafe-request-id header.

Retries

Retries are on by default: 2 attempts after the first, exponential backoff from 500ms capped at 5s, 25% jitter subtracted, on status 408, 429, 500, 502, 503, 504, and 529. Network errors are retried too. Retry-After and retry-after-ms response headers are honored when they exceed the computed backoff.

client, err := typesafe.NewClient(typesafe.WithRetryPolicy(typesafe.RetryPolicy{
	MaxRetries:     5,
	InitialBackoff: 200 * time.Millisecond,
	MaxBackoff:     10 * time.Second,
	BackoffJitter:  0.3,
}))

typesafe.NoRetryPolicy() turns retries off. A zero-value RetryPolicy is also valid and disables retries; individual zero fields fall back to the documented defaults.

Client options

Option Default Notes
WithAPIKey TYPESAFE_API_KEY Required, from option or environment
WithBaseURL https://api.typesafe.ai Also TYPESAFE_BASE_URL; HTTPS enforced except on localhost
WithDefaultModel jev-latest Also TYPESAFE_DEFAULT_MODEL
WithHTTPClient &http.Client{} Bring your own transport, proxy, or pool
WithTimeout 10s Per request, including retries individually
WithRetryPolicy 2 retries See above
WithLogger none *slog.Logger; bodies and keys never logged
WithUserAgentSuffix none Appends to the SDK User-Agent

Per-request options

Every API method takes trailing RequestOption values that override the client for that one call:

resp, err := client.SystemOne(ctx, req,
	typesafe.WithRequestTimeout(30*time.Second),
	typesafe.WithRequestRetryPolicy(typesafe.NoRetryPolicy()),
	typesafe.WithExtraHeaders(http.Header{"X-Trace-Id": {traceID}}),
)

WithExtraHeaders will not replace headers the SDK controls: Authorization, User-Agent, Content-Type, Accept, X-TypeSafe-SDK, X-TypeSafe-Runtime, and X-TypeSafe-Retry-Count.

Concurrency

A Client is safe for concurrent use by multiple goroutines. Create one per process and share it; do not copy a Client after first use.

Forward compatibility

Answer types this SDK version does not model are not an error. Their JSON is preserved in Answer.Raw, and the typed accessors skip them, so a new answer type on the API side will not break a running deployment:

if _, err := answer.AsNoul(); err != nil {
	log.Printf("unhandled answer type %q: %s", answer.Type, answer.Raw)
}

Development

go test ./...              # run the suite
go test -race -cover ./... # race detector and coverage
gofmt -l .                 # must print nothing
go vet ./...

See CONTRIBUTING.md for the full checklist.

License and affiliation

MIT — this client library only.

This project is unofficial and independent. It is not affiliated with, endorsed by, or supported by TypeSafe AI, and the MIT license covers this client code alone, not the TypeSafe API or any TypeSafe service. Your use of the API remains governed by whatever terms you have agreed with TypeSafe.

Documentation

Overview

Package typesafe provides a Go client SDK for the TypeSafe AI System One API.

This is an unofficial, community-built SDK, maintained independently at https://github.com/sachin-handiekar/typesafe-go. It is not affiliated with, endorsed by, or supported by TypeSafe AI. Report problems with this library on its issue tracker, not to TypeSafe support.

TypeSafe's System One models make fast, structured decisions for software. Send state and typed questions; get structured answers your code can use directly.

Quick start

client, err := typesafe.NewClient(typesafe.WithAPIKey("your-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.",
    Model: typesafe.ModelJevLatest,
    Questions: typesafe.Questions{
        "is_urgent": typesafe.Noul("Does this convey urgency?"),
    },
})
if err != nil {
    log.Fatal(err)
}

noul, _ := resp.Answers["is_urgent"].AsNoul()
fmt.Printf("Urgent: %.0f%%\n", noul.Value*100) // Urgent: 95%

Authentication

The client reads credentials in this order:

  1. Explicit WithAPIKey option
  2. TYPESAFE_API_KEY environment variable

Concurrency

A Client is safe for concurrent use by multiple goroutines. Do not copy a Client after first use.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

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: "Help! My payouts have been failing for 3 days.",
		Questions: typesafe.Questions{
			"is_urgent": typesafe.Noul("Does this convey urgency?"),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	urgent, err := resp.Answers["is_urgent"].AsNoul()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Urgent: %.0f%%\n", urgent.Value*100)
}

Index

Examples

Constants

View Source
const (
	// Version is the SDK version.
	Version = "0.1.1"

	// DefaultBaseURL is the default TypeSafe API base URL.
	DefaultBaseURL = "https://api.typesafe.ai"

	// DefaultTimeout is the default HTTP request timeout.
	DefaultTimeout = 10 * time.Second

	// DefaultModel is the default model name, matching the Python SDK.
	DefaultModel = "jev-latest"

	// EnvAPIKey is the environment variable for the API key.
	//nolint:gosec // G101 false positive: this is an environment variable name, not a credential.
	EnvAPIKey = "TYPESAFE_API_KEY"

	// EnvBaseURL is the environment variable for the base URL.
	EnvBaseURL = "TYPESAFE_BASE_URL"

	// EnvDefaultModel is the environment variable for the default model.
	EnvDefaultModel = "TYPESAFE_DEFAULT_MODEL"
)
View Source
const (
	// ModelJevLatest points to the most recent stable, official Jev release.
	ModelJevLatest = "jev-latest"

	// ModelJevPreview points to the most recent release, whether or not
	// it is an official one.
	ModelJevPreview = "jev-preview"

	// ModelJev1_13_0 is the versioned ID for Jev 1.13.0.
	ModelJev1_13_0 = "jev-1.13.0"
)

Model aliases and versioned IDs.

Variables

View Source
var (
	// ErrBadRequest is returned for 400 responses.
	ErrBadRequest = errors.New("typesafe: bad request")

	// ErrUnauthorized is returned for 401 responses.
	ErrUnauthorized = errors.New("typesafe: unauthorized")

	// ErrForbidden is returned for 403 responses.
	ErrForbidden = errors.New("typesafe: forbidden")

	// ErrNotFound is returned for 404 responses.
	ErrNotFound = errors.New("typesafe: not found")

	// ErrUnprocessableEntity is returned for 422 responses.
	ErrUnprocessableEntity = errors.New("typesafe: unprocessable entity")

	// ErrRateLimited is returned for 429 responses.
	ErrRateLimited = errors.New("typesafe: rate limited")

	// ErrOverloaded is returned for 529 responses.
	ErrOverloaded = errors.New("typesafe: overloaded")

	// ErrInternalServer is returned for 5xx responses.
	ErrInternalServer = errors.New("typesafe: internal server error")
)

Sentinel errors used with errors.Is for common HTTP status codes.

View Source
var ErrResponseTooLarge = transport.ErrResponseTooLarge

ErrResponseTooLarge is returned when a successful response body exceeds the SDK's read limit. The response is not decoded and the request is not retried, since a retry would return the same oversized body.

Functions

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a 404 Not Found error.

func IsOverloaded

func IsOverloaded(err error) bool

IsOverloaded reports whether err is a 529 Overloaded error.

func IsRateLimited

func IsRateLimited(err error) bool

IsRateLimited reports whether err is a 429 Too Many Requests error.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is a 401 Unauthorized error.

func IsUnprocessableEntity

func IsUnprocessableEntity(err error) bool

IsUnprocessableEntity reports whether err is a 422 Unprocessable Entity error.

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status code.
	StatusCode int

	// Message is a human-readable error message extracted from the response body.
	Message string

	// RequestID is the value of the x-typesafe-request-id header, if present.
	RequestID string

	// Body is the raw response body, truncated to [maxErrorBodyLen].
	Body string
	// contains filtered or unexported fields
}

APIError is returned when the TypeSafe API responds with a non-2xx status.

Example

Inspect an *APIError for the status code and the request ID to quote in a support thread, and use the sentinel helpers for common cases.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	_, err = client.SystemOne(context.Background(), &typesafe.SystemOneRequest{
		State:     "...",
		Questions: typesafe.Questions{"ok": typesafe.Noul("Is this fine?")},
	})
	if err != nil {
		var apiErr *typesafe.APIError
		if errors.As(err, &apiErr) {
			fmt.Printf("status=%d request_id=%s: %s\n",
				apiErr.StatusCode, apiErr.RequestID, apiErr.Message)
		}

		switch {
		case typesafe.IsRateLimited(err):
			fmt.Println("slow down")
		case typesafe.IsOverloaded(err):
			fmt.Println("try again shortly")
		case typesafe.IsUnauthorized(err):
			fmt.Println("check the API key")
		}
	}
}

func (*APIError) Error

func (e *APIError) Error() string

Error returns the formatted error message with status code and request ID.

func (*APIError) Is

func (e *APIError) Is(target error) bool

Is reports whether the error matches a sentinel error. This enables usage like:

if errors.Is(err, typesafe.ErrRateLimited) { ... }

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap returns the underlying error, if any.

type Answer

type Answer struct {
	// Type matches the question type that produced this answer.
	Type QuestionType `json:"type"`

	// Raw holds the undecoded JSON for this answer, useful for forward
	// compatibility with future answer types.
	Raw json.RawMessage `json:"-"`
	// contains filtered or unexported fields
}

Answer is a polymorphic answer returned by the API. Use Answer.AsNoul, Answer.AsChoice, or Answer.AsScore to access type-specific fields.

Unknown answer types (from future API versions) are preserved in Answer.Raw and silently skipped by the typed accessors.

func (Answer) AsChoice

func (a Answer) AsChoice() (ChoiceAnswer, error)

AsChoice returns a typed ChoiceAnswer. Returns an error if the answer type is not "choice".

func (Answer) AsNoul

func (a Answer) AsNoul() (NoulAnswer, error)

AsNoul returns a typed NoulAnswer. Returns an error if the answer type is not "noul".

func (Answer) AsScore

func (a Answer) AsScore() (ScoreAnswer, error)

AsScore returns a typed ScoreAnswer. Returns an error if the answer type is not "score".

Example

AsScore reports the probability-weighted score, which can land between levels, along with the legend and per-level probabilities.

package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	var a typesafe.Answer
	const payload = `{"type":"score","score":2.4,"legend":{"1":"low","2":"mid","3":"high"},` +
		`"probabilities":{"1":0.1,"2":0.4,"3":0.5},"confidence":0.62}`
	if err := json.Unmarshal([]byte(payload), &a); err != nil {
		log.Fatal(err)
	}

	s, err := a.AsScore()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("score=%.1f confidence=%.2f top=%s\n", s.Score, s.Confidence, s.Legend["3"])
}
Output:
score=2.4 confidence=0.62 top=high

func (Answer) MarshalJSON

func (a Answer) MarshalJSON() ([]byte, error)

MarshalJSON encodes the answer back to JSON. If Raw is available it is returned directly; otherwise the typed fields are serialized.

This is a value method so that marshaling an Answer, a *Answer, or a struct with an Answer field all produce the same output.

func (*Answer) UnmarshalJSON

func (a *Answer) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes an answer from JSON, preserving unknown answer types for forward compatibility.

type ChoiceAnswer

type ChoiceAnswer struct {
	// Choice is the highest-probability option.
	Choice string

	// Probabilities maps every option to its probability (floats that sum to 1).
	Probabilities map[string]float64

	// Confidence is how certain the model is, derived from probabilities (0 to 1).
	Confidence float64
}

ChoiceAnswer is a typed choice answer.

type Client

type Client struct {

	// Models provides access to the models listing endpoint.
	Models *ModelsService
	// contains filtered or unexported fields
}

Client is the TypeSafe API client. It is safe for concurrent use by multiple goroutines. Do not copy a Client after first use.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient creates a new TypeSafe client with the given options. It returns an error if the configuration is invalid (e.g. missing API key).

Credentials are resolved in this order:

  1. Explicit WithAPIKey option
  2. TYPESAFE_API_KEY environment variable
Example
package main

import (
	"log"
	"os"
	"time"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient(
		typesafe.WithAPIKey(os.Getenv("MY_TYPESAFE_KEY")),
		typesafe.WithDefaultModel(typesafe.ModelJevLatest),
		typesafe.WithTimeout(30*time.Second),
		typesafe.WithUserAgentSuffix("my-service/1.4"),
	)
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

func (*Client) SystemOne

func (c *Client) SystemOne(ctx context.Context, req *SystemOneRequest, opts ...RequestOption) (*SystemOneResponse, error)

SystemOne evaluates the state against a set of typed questions and returns structured answers. This is the primary API method.

See https://docs.typesafe.ai/api for the full endpoint reference.

Example

Ask several typed questions in one call and read each answer back by name.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	resp, err := client.SystemOne(context.Background(), &typesafe.SystemOneRequest{
		State: []map[string]string{
			{"role": "customer", "text": "Where is my refund? It has been two weeks."},
			{"role": "agent", "text": "Let me look into that for you."},
		},
		Questions: typesafe.Questions{
			"needs_escalation": typesafe.Noul("Should a human take over?"),
			"topic": typesafe.Choice("What is this about?", map[string]any{
				"billing":   "payments, invoices, refunds",
				"technical": "bugs, outages, integration problems",
				"other":     nil,
			}),
			"severity": typesafe.Score("How severe is this?", []any{
				"cosmetic, no user impact",
				"degraded but usable",
				"fully broken, revenue affected",
			}),
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	for name, n := range resp.Nouls() {
		fmt.Printf("%s: %.2f\n", name, n.Value)
	}
	for name, c := range resp.Choices() {
		fmt.Printf("%s: %s (confidence %.2f)\n", name, c.Choice, c.Confidence)
	}
	for name, s := range resp.Scores() {
		fmt.Printf("%s: %.2f\n", name, s.Score)
	}

	fmt.Printf("model=%s tokens=%d request_id=%s\n",
		resp.Model, resp.Usage.InputTokens, resp.RequestID)
}
Example (RequestOptions)

Per-request options override the client for a single call.

package main

import (
	"context"
	"log"
	"net/http"
	"time"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	_, err = client.SystemOne(context.Background(),
		&typesafe.SystemOneRequest{
			State:     "a very large batch of state",
			Questions: typesafe.Questions{"ok": typesafe.Noul("Is this fine?")},
		},
		typesafe.WithRequestTimeout(2*time.Minute),
		typesafe.WithRequestRetryPolicy(typesafe.NoRetryPolicy()),
		typesafe.WithExtraHeaders(http.Header{"X-Trace-Id": {"abc-123"}}),
	)
	if err != nil {
		log.Fatal(err)
	}
}

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error is the base error type for SDK failures that are not HTTP responses.

func (*Error) Error

func (e *Error) Error() string

Error returns the error message.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying error, if any.

type ListModelsResponse

type ListModelsResponse struct {
	// Models is the list of available models.
	Models []ModelCard `json:"models"`

	// RequestID is the x-typesafe-request-id header from the response.
	RequestID string `json:"-"`
}

ListModelsResponse is the response from GET /v1/models.

type ModelCard

type ModelCard struct {
	// Name is the model ID or alias, as accepted by the model field.
	Name string `json:"name"`

	// Description is a human-readable description of the model.
	Description string `json:"description"`

	// ReleaseDate is the model release date, formatted as YYYY-MM-DD.
	ReleaseDate string `json:"release_date"`
}

ModelCard describes a single model returned by the List Models endpoint.

type ModelsService

type ModelsService struct {
	// contains filtered or unexported fields
}

ModelsService provides access to the models listing endpoint, reached through Client.Models.

func (*ModelsService) List

List returns the models available to the account.

See https://docs.typesafe.ai/models for details on model names and aliases.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	models, err := client.Models.List(context.Background())
	if err != nil {
		log.Fatal(err)
	}
	for _, m := range models.Models {
		fmt.Printf("%s (%s): %s\n", m.Name, m.ReleaseDate, m.Description)
	}
}

type NoulAnswer

type NoulAnswer struct {
	// Value is the probability the answer is yes, from 0 (no) to 1 (yes).
	Value float64
}

NoulAnswer is a typed yes/no answer.

type NoulCriteria

type NoulCriteria struct {
	// True describes what a yes (value near 1) means.
	True any `json:"true,omitempty"`

	// False describes what a no (value near 0) means.
	False any `json:"false,omitempty"`
}

NoulCriteria describes what yes and no mean for a Noul question.

type Option

type Option func(*clientConfig) error

Option configures a Client. Use the With* functions to create options.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key. If not provided, the TYPESAFE_API_KEY environment variable is used.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the default API base URL.

func WithDefaultModel

func WithDefaultModel(model string) Option

WithDefaultModel sets the default model used when SystemOneRequest.Model is empty.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets a custom http.Client.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets a structured logger. By default no logging occurs. Request and response bodies are never logged.

func WithRetryPolicy

func WithRetryPolicy(p RetryPolicy) Option

WithRetryPolicy sets a custom retry policy.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-request timeout.

func WithUserAgentSuffix

func WithUserAgentSuffix(suffix string) Option

WithUserAgentSuffix appends a custom suffix to the User-Agent header.

type Question

type Question struct {
	// Type identifies the question kind.
	Type QuestionType `json:"type"`

	// Instructions is the question to evaluate. Accepts string, map, or slice.
	Instructions any `json:"instructions,omitempty"`

	// Criteria varies by type: [NoulCriteria] for noul, map[string]any for choice,
	// []any for score. Use the constructors instead of setting this directly.
	Criteria any `json:"criteria,omitempty"`
}

Question is a typed question to evaluate against the state. Use the Noul, NoulWithCriteria, Choice, or Score constructors.

func Choice

func Choice(instructions any, criteria map[string]any) Question

Choice creates a question that picks one option from a defined set. The criteria map keys are option names; values are descriptions (use nil when an option needs no extra detail). Maximum 255 options.

See https://docs.typesafe.ai/primitives/choice for details.

Example

Choice picks one option from a set. A nil value means the option needs no extra description.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	q := typesafe.Choice("What is this about?", map[string]any{
		"billing": "payments, invoices, refunds",
		"other":   nil,
	})

	out, _ := json.Marshal(q)
	fmt.Println(string(out))
}
Output:
{"criteria":{"billing":"payments, invoices, refunds","other":null},"instructions":"What is this about?","type":"choice"}

func Noul

func Noul(instructions any) Question

Noul creates a yes/no question. Returns the probability the answer is yes.

The instructions parameter accepts a string, map, or slice. See https://docs.typesafe.ai/primitives/noul for details.

Example

Noul asks a yes/no question and gets back a probability.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	q := typesafe.Noul("Does this convey urgency?")

	out, _ := json.Marshal(q)
	fmt.Println(string(out))
}
Output:
{"instructions":"Does this convey urgency?","type":"noul"}

func NoulWithCriteria

func NoulWithCriteria(instructions any, criteria NoulCriteria) Question

NoulWithCriteria creates a yes/no question with explicit criteria describing what yes and no mean.

Example

NoulWithCriteria spells out what yes and no mean when they are ambiguous.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	q := typesafe.NoulWithCriteria("Is this spam?", typesafe.NoulCriteria{
		True:  "unsolicited bulk promotion",
		False: "a real message from a real person",
	})

	out, _ := json.Marshal(q)
	fmt.Println(string(out))
}
Output:
{"criteria":{"true":"unsolicited bulk promotion","false":"a real message from a real person"},"instructions":"Is this spam?","type":"noul"}

func Score

func Score(instructions any, criteria []any) Question

Score creates a question that rates the state along an ordered rubric. The criteria slice must have at least 2 and at most 10 level descriptions.

See https://docs.typesafe.ai/primitives/score for details.

Example

Score rates the state along an ordered rubric of 2 to 10 levels.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	q := typesafe.Score("How severe is this?", []any{
		"cosmetic, no user impact",
		"degraded but usable",
		"fully broken, revenue affected",
	})

	out, _ := json.Marshal(q)
	fmt.Println(string(out))
}
Output:
{"criteria":["cosmetic, no user impact","degraded but usable","fully broken, revenue affected"],"instructions":"How severe is this?","type":"score"}

func (Question) MarshalJSON

func (q Question) MarshalJSON() ([]byte, error)

marshalQuestion produces the JSON form, omitting nil-valued optional fields while preserving explicit null values nested inside criteria/instructions.

type QuestionType

type QuestionType string

QuestionType identifies the kind of question.

const (
	// QuestionTypeNoul is a yes/no question returning a probability.
	QuestionTypeNoul QuestionType = "noul"

	// QuestionTypeChoice picks one option from a defined set.
	QuestionTypeChoice QuestionType = "choice"

	// QuestionTypeScore rates state along an ordered rubric.
	QuestionTypeScore QuestionType = "score"
)

type Questions

type Questions map[string]Question

Questions is a named map of questions. You choose each key; answers come back under the same keys.

type RequestOption

type RequestOption func(*requestConfig)

RequestOption configures a single API call, overriding client-level settings.

func WithExtraHeaders

func WithExtraHeaders(h http.Header) RequestOption

WithExtraHeaders adds additional headers to a single request.

Headers the SDK sets itself are reserved and silently dropped: Authorization, User-Agent, Content-Type, Accept, X-TypeSafe-SDK, X-TypeSafe-Runtime, and X-TypeSafe-Retry-Count. Use WithUserAgentSuffix to extend the User-Agent.

func WithRequestRetryPolicy

func WithRequestRetryPolicy(p RetryPolicy) RequestOption

WithRequestRetryPolicy overrides the client retry policy for a single request. A negative RetryPolicy.MaxRetries is treated as zero.

func WithRequestTimeout

func WithRequestTimeout(d time.Duration) RequestOption

WithRequestTimeout overrides the client timeout for a single request.

type RetryPolicy

type RetryPolicy struct {
	// MaxRetries is the maximum number of retry attempts after the initial
	// request. Zero disables retries; a negative value is treated as zero.
	// Default: 2.
	MaxRetries int

	// InitialBackoff is the delay before the first retry. Default: 500ms.
	InitialBackoff time.Duration

	// MaxBackoff caps the backoff duration. Default: 5s.
	MaxBackoff time.Duration

	// BackoffJitter is the fraction of each backoff delay randomly subtracted,
	// between 0 and 1. Default: 0.25.
	BackoffJitter float64

	// RetryableStatuses is the set of HTTP status codes that trigger a retry.
	// Default: {408, 429, 500, 502, 503, 504, 529}.
	RetryableStatuses map[int]bool

	// RespectRetryAfter controls whether Retry-After and retry-after-ms
	// response headers are honored. Default: true.
	RespectRetryAfter *bool
	// contains filtered or unexported fields
}

RetryPolicy configures automatic retry behavior. The zero value is a valid policy that disables retries.

Example
package main

import (
	"log"
	"time"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient(typesafe.WithRetryPolicy(typesafe.RetryPolicy{
		MaxRetries:     5,
		InitialBackoff: 200 * time.Millisecond,
		MaxBackoff:     10 * time.Second,
		BackoffJitter:  0.3,
	}))
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns the default retry policy, matching the Python SDK's defaults.

func NoRetryPolicy

func NoRetryPolicy() RetryPolicy

NoRetryPolicy returns a policy that disables automatic retries.

Example

A zero-value RetryPolicy is valid and disables retries, as does NoRetryPolicy.

package main

import (
	"log"

	"github.com/sachin-handiekar/typesafe-go"
)

func main() {
	client, err := typesafe.NewClient(typesafe.WithRetryPolicy(typesafe.NoRetryPolicy()))
	if err != nil {
		log.Fatal(err)
	}
	_ = client
}

type ScoreAnswer

type ScoreAnswer struct {
	// Score is the probability-weighted value across the levels; can land between levels.
	Score float64

	// Legend maps each level number (as string) to its description.
	Legend map[string]string

	// Probabilities maps each level (as string key) to its probability (floats that sum to 1).
	Probabilities map[string]float64

	// Confidence is how certain the model is, derived from probabilities (0 to 1).
	Confidence float64
}

ScoreAnswer is a typed score answer.

type SystemOneRequest

type SystemOneRequest struct {
	// State is the content to evaluate. A plain string for text, or structured
	// data (object/array) for chat logs, records, or application state.
	State any `json:"state"`

	// Model selects which model handles the request. If empty, the client's
	// default model is used.
	Model string `json:"model,omitempty"`

	// Questions is a map of named, typed questions to evaluate against the
	// state.
	Questions Questions `json:"questions"`
}

SystemOneRequest is the request body for the System One evaluation endpoint.

type SystemOneResponse

type SystemOneResponse struct {
	// Model is the versioned model ID that performed the evaluation.
	Model string `json:"model"`

	// Answers contains one answer per question, keyed by the same IDs used
	// in the request's Questions map.
	Answers map[string]*Answer `json:"answers"`

	// Usage contains token usage for the request.
	Usage Usage `json:"usage"`

	// RequestID is the x-typesafe-request-id header from the response.
	RequestID string `json:"-"`
}

SystemOneResponse is the response from the System One evaluation endpoint.

func (*SystemOneResponse) Choices

func (r *SystemOneResponse) Choices() map[string]ChoiceAnswer

Choices returns only the choice answers, keyed by question name. Null answers in the response are skipped.

func (*SystemOneResponse) Nouls

func (r *SystemOneResponse) Nouls() map[string]NoulAnswer

Nouls returns only the noul answers, keyed by question name. Null answers in the response are skipped.

func (*SystemOneResponse) Scores

func (r *SystemOneResponse) Scores() map[string]ScoreAnswer

Scores returns only the score answers, keyed by question name. Null answers in the response are skipped.

type Usage

type Usage struct {
	// InputTokens is the number of billable input tokens.
	InputTokens int `json:"input_tokens"`

	// OutputTokens is the number of output tokens (currently free).
	OutputTokens int `json:"output_tokens"`
}

Usage reports token consumption for a request.

Directories

Path Synopsis
internal
transport
Package transport provides internal HTTP plumbing for the TypeSafe SDK.
Package transport provides internal HTTP plumbing for the TypeSafe SDK.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL