Help us shape the future of Go! Take the 2026 Go Developer Survey

typesafe

package module
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 25 Imported by: 0

README

TypeSafe AI Go SDK

CI Go Reference

Go SDK for TypeSafe AI — deterministic evaluation models that answer classification and rating questions about any text or JSON you already have.

Ask up to dozens of questions in one call, and get back probabilities, labels, and rubric scores instead of free-form text you have to parse.

  • Standard library only. net/http, encoding/json, and log/slog. No runtime dependencies.
  • Concurrency-safe by construction. A Client holds no per-request state and pools connections; every call takes a context.Context.
  • Resilient by default. Jittered exponential backoff with an overall retry budget and Retry-After support.
  • Typed everything. Typed errors you can match with errors.As, typed answers, and SystemOneAs[T] for your own response models.
  • A CLI when you want one. cmd/typesafe wraps the same client for shell scripts and pipelines; install it with go install or grab a binary from Releases.

Requires Go 1.26 or newer.


Installation

go get github.com/guchengod/typesafe-sdk-go
import typesafe "github.com/guchengod/typesafe-sdk-go"

Quickstart

Set your API key in the environment:

export TYPESAFE_API_KEY="ts_live_..."

Then create a client and ask questions about your content:

package main

import (
	"context"
	"fmt"
	"log"

	typesafe "github.com/guchengod/typesafe-sdk-go"
)

func main() {
	client, err := typesafe.NewClient() // reads TYPESAFE_API_KEY
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = client.Close() }()

	response, err := client.SystemOne(
		context.Background(),
		map[string]any{
			"channel":  "email",
			"document": "I was charged twice on my credit card for invoice #4912. Please refund immediately.",
		},
		typesafe.Questions{
			"billing": typesafe.NewNoul("Is this message about billing or payments?"),
			"category": typesafe.NewChoice(
				map[string]any{
					"billing":   "Charges, refunds, or invoices",
					"technical": "Bugs, crashes, or API problems",
					"other":     nil, // nil means the label has no description
				},
				typesafe.WithInstructions("What department should handle this ticket?"),
			),
			"urgency": typesafe.NewScore(
				[]any{"Can wait until next week", "Needs attention within 48 hours", "Requires same-day attention"},
				typesafe.WithInstructions("How urgently does this customer require a response?"),
			),
		},
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("billing p(true) = %.2f\n", response.Nouls()["billing"].Noul)
	fmt.Printf("category        = %s (confidence %.2f)\n", response.Choices()["category"].Choice, response.Choices()["category"].Confidence)
	fmt.Printf("urgency         = %.2f of 2\n", response.Scores()["urgency"].Score)
}

Patterns & Model Guidance

TypeSafe's Jev is a calibrated System One model designed for fast, discrete qualitative decisions rather than continuous generation or arithmetic calculation. Keep determinism and arithmetic in Go code, and use Jev for structured judgments.

1. Confidence-Gated Routing (置信度三段式路由)

Every Choice and Score answer includes a Confidence score between 0.0 and 1.0, derived from how probability mass concentrates across alternatives. Use confidence as an operational control axis — the thresholds below are an example, not an API-defined rule: the docs gate at 0.5 for a high-stakes decision (confidence) and at 0.6 for a moderation queue (confidence routing).

category := response.Choices()["category"]

switch {
case category.Confidence >= 0.85:
	// High confidence: act automatically without human intervention
	dispatchTicket(category.Choice)

case category.Confidence >= 0.50:
	// Medium confidence: proceed with caution, flag for confirmation
	flagForOperatorReview(category.Choice)

default:
	// Low confidence: model is genuinely unsure ("I don't know"); route to human triage
	routeToHumanTriage()
}
2. Counting & Speculative Fan-Out (计数与投机性扇出)

Jev is not an arithmetic calculator and does not count items reliably. When counting items that match a condition across a slice or list, keep the arithmetic in Go: fan out atomic Noul questions in a single SystemOne request, then sum the matching probabilities in code:

items := []string{"urgent payment bug", "typo in docs", "security vulnerability"}

questions := make(typesafe.Questions, len(items))
for i, item := range items {
	questions[fmt.Sprintf("item_%d", i)] = typesafe.NewNoul(
		fmt.Sprintf("Does this issue describe a severe production or security incident: %q?", item),
	)
}

// Jev ingests the state once and evaluates all questions in parallel
resp, err := client.SystemOne(ctx, map[string]any{"items": items}, questions)
if err != nil {
	log.Fatal(err)
}

// Aggregate in Go code
severeCount := 0
for i := range items {
	if resp.Nouls()[fmt.Sprintf("item_%d", i)].Noul >= 0.5 {
		severeCount++
	}
}
fmt.Printf("Severe issues: %d of %d\n", severeCount, len(items))
3. Score Rubric: Threshold Filtering vs Arithmetic (评分题准则)

ScoreAnswer.Score is the probability-weighted average across discrete levels (0 to len(levels)-1):

  • Do: Use score for threshold gating (e.g. urgency.Score >= 1.5).
  • Don't: Do not use score as a continuous linear interpolator to reconstruct exact numerical quantities between levels.
  • Bounds: Score rubrics require between 2 and 10 levels. Choice questions accept up to 255 options.

Command line

The repository ships a CLI, built on the standard library only, for use without writing Go:

# Install the latest release into $(go env GOPATH)/bin
go install github.com/guchengod/typesafe-sdk-go/cmd/typesafe@latest

# Or download a binary for your platform
# https://github.com/guchengod/typesafe-sdk-go/releases
export TYPESAFE_API_KEY="ts_live_..."

typesafe models --format text

typesafe ask \
  --state '{"body": "I was charged twice on invoice #4912."}' \
  --noul 'billing=Is this about billing?' \
  --choice 'queue=billing:Charges,technical:API issues,other' \
  --score 'urgency=routine,elevated,critical' \
  --format text
model    jev-1.13.0
usage    Usage{input_tokens: 367, output_tokens: 69}
billing  noul 0.9900
queue    choice billing (confidence 1.00)
urgency  score 0.53 of 2 (confidence 0.20)

--format json (the default) prints the API response, so it composes with jq:

curl -s https://example.test/ticket.json | typesafe ask --state - --questions questions.json | jq '.answers'

Exit codes tell a script what happened: 0 success, 1 usage error, 2 the API returned an error status, 3 the request never reached the API. See the CLI guide for every flag.


Documentation

Full guides live in the wiki:

Guide Covers
Use Cases What people build with it, with a worked triage example
Questions The noul, choice, and score primitives, their options, and raw questions
Responses Response accessors and the decoding rules
Custom Response Models Decoding straight into your own struct with SystemOneAs
Configuration Client options, environment variables, per-call options
Retry Policy Retries, backoff, and the retry budget
Errors The typed error taxonomy and how to match it
Logging Structured logging and credential redaction
Security Redaction, protected headers, redirects, bounded reads, TLS
Concurrency Sharing one client across goroutines
CLI The typesafe command: flags, output formats, exit codes
Examples Runnable programs in this repository

Also see:


Examples

Example Shows
examples/quickstart One SystemOne call with all three primitives, printing every answer.
examples/advanced SystemOneAs with a custom model, Models.List, per-call options, WithExtraBody, typed error handling, and a redacting logger.
export TYPESAFE_API_KEY="ts_live_..."
go run ./examples/quickstart
go run ./examples/advanced

Contributing

Issues and pull requests are welcome at github.com/guchengod/typesafe-sdk-go.

go build ./...
go vet ./...
go test ./...          # unit tests
go test -run TestIntegration ./...   # live tests, need TYPESAFE_API_KEY
go test -bench . -run XXX ./...      # benchmarks, no network
golangci-lint run ./...              # linters
go build ./cmd/typesafe              # the CLI

License

MIT License. See LICENSE for details.

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

Examples

Constants

View Source
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.

View Source
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.

View Source
const (
	SystemOnePath = "/v1/systemone"
	ModelsPath    = "/v1/models"
)

API Endpoint paths.

View Source
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.

View Source
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

func DefaultRetryStatuses() map[int]bool

DefaultRetryStatuses returns the set of HTTP status codes the SDK retries by default: 408, 429, and every 5xx.

func NormalizeQuestions

func NormalizeQuestions(questions any) (map[string]any, error)

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

func ParseLogLevel(level string) (slog.Level, bool)

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

func AsAPIError(err error) (*APIError, bool)

AsAPIError extracts the APIError carried by any HTTP failure this SDK returns. It reports false for connection, timeout, validation-of-input, and configuration errors.

func (*APIError) Error

func (e *APIError) Error() string

Error returns the status and message with the available request context.

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

Error returns the formatted validation message.

func (*APIResponseValidationError) Unwrap

func (e *APIResponseValidationError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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

type Answers map[string]Answer

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

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

UnmarshalJSON decodes the answers object, dispatching each entry on its "type" field.

type AuthenticationError

type AuthenticationError struct{ APIError }

AuthenticationError is an HTTP 401 response.

func (*AuthenticationError) Unwrap

func (e *AuthenticationError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

type BadRequestError

type BadRequestError struct{ APIError }

BadRequestError is an HTTP 400 response.

func (*BadRequestError) Unwrap

func (e *BadRequestError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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

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

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

func (c *Client) Close() error

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

func (c *Client) Config() *Config

Config returns the resolved client configuration.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

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.

func (*InternalServerError) Unwrap

func (e *InternalServerError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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

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 NotFoundError

type NotFoundError struct{ APIError }

NotFoundError is an HTTP 404 response.

func (*NotFoundError) Unwrap

func (e *NotFoundError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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

func WithAPIKey(apiKey string) Option

WithAPIKey sets the API key, overriding the TYPESAFE_API_KEY environment variable.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API root, overriding the TYPESAFE_BASE_URL environment variable.

func WithDefaultModel

func WithDefaultModel(model string) Option

WithDefaultModel sets the model used when a call does not name one, overriding the TYPESAFE_DEFAULT_MODEL environment variable.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient supplies the http.Client the SDK sends through, together with its transport. Mutually exclusive with WithTransport.

func WithHeader

func WithHeader(key, value string) Option

WithHeader sets a default header sent with every request. Client identification and credentials are applied afterwards and cannot be displaced.

func WithHeaders

func WithHeaders(headers map[string]string) Option

WithHeaders sets default headers sent with every request, merging with any set by WithHeader.

func WithLogger

func WithLogger(logger *slog.Logger) Option

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

func WithTimeout(timeout time.Duration) Option

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.

func (*OverloadedError) Unwrap added in v0.10.1

func (e *OverloadedError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

type PermissionDeniedError

type PermissionDeniedError struct{ APIError }

PermissionDeniedError is an HTTP 403 response.

func (*PermissionDeniedError) Unwrap

func (e *PermissionDeniedError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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

type Questions map[string]Question

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.

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

type RawQuestion

type RawQuestion map[string]any

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

func (r *RedactingHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled reports whether the wrapped handler records the level.

func (*RedactingHandler) Handle

func (r *RedactingHandler) Handle(ctx context.Context, record slog.Record) error

Handle redacts the record's attributes and forwards it to the wrapped handler. Attributes that carry no credential are passed through unchanged.

func (*RedactingHandler) WithAttrs

func (r *RedactingHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a handler whose records carry the given attributes.

func (*RedactingHandler) WithGroup

func (r *RedactingHandler) WithGroup(name string) slog.Handler

WithGroup returns a handler that groups subsequent attributes under name.

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

func (p *RetryPolicy) Execute(ctx context.Context, fn func(attempt int) error) error

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.

func (*SDKError) Error

func (e *SDKError) Error() string

func (*SDKError) Unwrap

func (e *SDKError) Unwrap() error

Unwrap returns the wrapped cause so errors.Is and errors.As traverse it.

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.

func (*UnprocessableEntityError) Unwrap

func (e *UnprocessableEntityError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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.

func (Usage) String

func (u Usage) String() string

String renders the token counts, showing an unreported count as "-".

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.

Jump to

Keyboard shortcuts

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