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

typesafe

package module
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 29 Imported by: 0

README

TypeSafe AI Go SDK

CI Go Reference

English | 简体中文


TypeSafe AI 官方 Go 语言客户端 SDK —— 用于对任意文本、结构化数据或图像进行确定性定性判断、分类评级与目标定位(System One 定性决策模型)。

单次调用可并发评估数十个问题,直接返回校准概率、选项标签、评分以及边界框坐标,无需解析和清洗自由格式的大模型文本。

  • 纯标准库实现:仅依赖 net/http、encoding/json 与 log/slog,零外部依赖,极速编译。
  • 天然并发安全:Client 不保存每请求可变状态,底层连接池高效复用,方法均支持 context.Context。
  • 自动透明压缩:请求体 $\ge 1\text{ KiB}$ 自动启用标准 Gzip 压缩,大幅缩减网络带宽和传输延迟。
  • 原生多模态与目标定位:全面支持 Image 图片资产与 Box 图像目标检测边界框问答。
  • 健壮默认重试:基于指数退避加抖动的自适应重试,深度支持服务端 Retry-After 标头。
  • 强类型设计:全链路结构体类型支持,可配合 errors.As 提取细分错误,亦可通过 SystemOneAs[T] 反序列化到自定义业务结构体。
  • 开箱即用 CLI:附带纯标准库打造的命令行工具 cmd/typesafe,完美适配 Shell 脚本与流水线。

需要 Go 1.26 或更高版本。


目录


安装

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

快速上手

配置环境变量 TYPESAFE_API_KEY:

export TYPESAFE_API_KEY="ts_live_..."

编写代码发起评估:

package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	client, err := typesafe.NewClient() // 自动读取 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": "信用卡账单 #4912 被重复扣费了,请立即为我办理退款。",
		},
		typesafe.Questions{
			"billing": typesafe.NewNoul("该内容是否与账单或退款相关?"),
			"category": typesafe.NewChoice(
				map[string]any{
					"billing":   "扣费、退款或发票问题",
					"technical": "系统故障、崩溃或 API 问题",
					"other":     nil, // nil 表示该选项无额外细化描述
				},
				typesafe.WithInstructions("该工单应指派到哪个部门?"),
			),
			"urgency": typesafe.NewScore(
				[]any{"可等待至下周", "需在 48 小时内处理", "需当天立即处理"},
				typesafe.WithInstructions("客户情绪和问题的紧急程度如何?"),
			),
		},
	)
	if err != nil {
		log.Fatal(err)
	}

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

核心问答原语

TypeSafe 提供了四种面向结构化决策的确定性原子问题类型:

原语 构造函数 输出结果 典型场景
Noul NewNoul(instruction, ...) NoulAnswer.Noul (0.0 ~ 1.0 的 Yes 概率) 二元判断、安全合规审核、意图判别
Choice NewChoice(criteria, ...) ChoiceAnswer.Choice (最高概率选项), Probabilities, Confidence 多分类路由、工单分流(最多支持 255 个选项)
Score NewScore(criteria, ...) ScoreAnswer.Score (概率加权期望分), Confidence, Legend 评级打分、情绪指数、优先级评定(支持 2~10 级)
Box NewBox(instruction, ...) BoxAnswer.Box (上下左右边界分数), Confidence 图像目标定位、目标裁剪与坐标识别

多模态图像与目标定位 (Box)

TypeSafe 支持在 state、问题指令或准则中直接包含图像,并使用 Box 原语在图像中精准定位物体。

开启图片或 Box 问题需要使用 jev-preview 模型,并附带 TypeSafe-Danger-Zone-Images: true 标头(通过 typesafe.WithImages(true) 或调用选项 typesafe.WithCallImages(true) 开启)。

package main

import (
	"context"
	"fmt"
	"log"

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

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

	// 从文件加载图像
	photo, err := typesafe.NewImageFromFile("kitchen.jpg")
	if err != nil {
		log.Fatal(err)
	}

	// 图像可直接嵌入 state 中,SDK 将自动收集并维护引用关系
	resp, err := client.SystemOne(
		context.Background(),
		map[string]any{"photo": photo},
		typesafe.Questions{
			"has_cup": typesafe.NewNoul("`photo` 中是否有咖啡杯?"),
			"cup_box": typesafe.NewBox("`photo` 中的咖啡杯"),
		},
		typesafe.WithModel(typesafe.ModelJevPreview),
	)
	if err != nil {
		log.Fatal(err)
	}

	if resp.Nouls()["has_cup"].Noul > 0.8 {
		box := resp.Boxes()["cup_box"].Box
		fmt.Printf("找到咖啡杯!边界: Top=%.2f, Bottom=%.2f, Left=%.2f, Right=%.2f\n",
			box.Top, box.Bottom, box.Left, box.Right)
	}
}

结构化泛型解码 (SystemOneAs)

使用 SystemOneAs[T] 可直接将返回结果映射到自定义业务结构体中:

type TicketAnalysis struct {
	Model    string                  `json:"model"`
	Usage    typesafe.Usage          `json:"usage"`
	Billing  typesafe.NoulAnswer     `json:"billing"`
	Category typesafe.ChoiceAnswer   `json:"category"`
	Urgency  typesafe.ScoreAnswer    `json:"urgency"`
	Target   *typesafe.BoxAnswer     `json:"target,omitempty"`
}

result, err := typesafe.SystemOneAs[TicketAnalysis](ctx, client, state, questions)
if err != nil {
	log.Fatal(err)
}
fmt.Println(result.Category.Choice, result.Billing.Noul)

模型使用规范与模式指南

TypeSafe 的 Jev 模型专为离散决策与定性判断设计,输出为校准概率分布。请将算术计算和确定性逻辑保留在 Go 代码中:

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

每个 Choice 和 Score 答案都包含 Confidence (0.0 ~ 1.0)。推荐使用三段式分流策略:

  • 高置信度 ($\ge 0.85$):模型极度确信,执行全自动化分支;
  • 中置信度 ($0.50 \sim 0.85$):模型倾向某选项但有轻微歧义,标记给操作员复核;
  • 低置信度 ($< 0.50$):模型明确表达“我不确定”,回退到人工兜底流程。
2. 计数与投机性扇出 (Counting & Speculative Fan-Out)

Jev 不擅长数数。统计数量时请在 Go 中执行计算:在一通 SystemOne 请求中扇出多个原子的 Noul 问题,再由 Go 代码汇总满足条件的计数。

3. Score 评分准则边界
  • 严禁将 score 作为连续实数用于插值还原数学物理量;
  • Score 用于阈值门禁过滤(如 score >= 1.5);
  • Score 级数限制为 2 至 10 级,Choice 选项最多 255 个。

🤖 AI Agent 专用系统提示词

如果你正在使用 AI 编程助手(如 Cursor、Claude Code、GitHub Copilot、Windsurf、Antigravity 或各类 Coding Agent),将以下提示词添加到项目中的 .cursorrules、CLAUDE.md 或 Agent 系统提示词中,可以让 Agent 零失误地直接调用和编写本库:

### TypeSafe AI Go SDK Coding Guidelines for Agent

You are working with the official TypeSafe AI Go SDK (`github.com/guchengod/typesafe-sdk-go`).
TypeSafe uses calibrated System One models (Jev) for deterministic classification, scoring, verification, and bounding-box object location. It does NOT generate unstructured free text.

#### 1. Core Principles
- Keep arithmetic, counting, string parsing, and business logic strictly in Go. Use Jev strictly for qualitative judgments.
- Always reuse a single `*typesafe.Client` (concurrency-safe, pools connections). Close it when done.
- Pass `context.Context` to all API calls.

#### 2. Initializing Client
```go
import typesafe "github.com/guchengod/typesafe-sdk-go"

client, err := typesafe.NewClient(
    // Optional configurations:
    // typesafe.WithAPIKey("ts_live_..."), // defaults to TYPESAFE_API_KEY env var
    // typesafe.WithTimeout(30 * time.Second),
    // typesafe.WithImages(true), // Danger zone: enables image & box questions
    // typesafe.WithLongContext(true), // Danger zone: enables 262k token window
    // typesafe.WithCompression(true), // Enabled by default for bodies >= 1 KiB
)
```

#### 3. Defining Questions
Use `typesafe.Questions` (map[string]typesafe.Question):
- **Yes/No Probability**: `typesafe.NewNoul("Question text", opts...)`
  Result: `response.Nouls()["key"].Noul` (float64, 0.0=No, 1.0=Yes)
- **Categorization**: `typesafe.NewChoice(map[string]any{"label": "description", "other": nil}, typesafe.WithInstructions("..."))`
  Max 255 options. Result: `response.Choices()["key"].Choice` (string), `.Confidence` (float64)
- **Rubric Rating**: `typesafe.NewScore([]any{"level0", "level1", "level2"}, typesafe.WithInstructions("..."))`
  Between 2 and 10 levels. Result: `response.Scores()["key"].Score` (float64, weighted expectation), `.Confidence` (float64)
- **Object Localization**: `typesafe.NewBox("the target object in `photo`")`
  Requires `typesafe.WithModel(typesafe.ModelJevPreview)` and `typesafe.WithImages(true)` or `typesafe.WithCallImages(true)`.
  Result: `response.Boxes()["key"].Box` (`Top`, `Bottom`, `Left`, `Right` in fractions 0..1), `.Confidence` (*float64)

#### 4. Multimodal Images
```go
img, err := typesafe.NewImageFromFile("path/to/image.jpg") // PNG or JPEG, max 16 MiB
// Embed img directly in state map or slice. The SDK automatically serializes it to a reference string and populates the assets payload.
resp, err := client.SystemOne(ctx, map[string]any{"photo": img}, questions,
    typesafe.WithModel(typesafe.ModelJevPreview),
    typesafe.WithCallImages(true),
)
```

#### 5. Confidence-Gated Decision Routing
Always inspect `Confidence` on Choice/Score:
- `Confidence >= 0.85`: High certainty, trigger automated action.
- `0.50 <= Confidence < 0.85`: Ambiguous, flag for human review.
- `Confidence < 0.50`: Uncertain, route to human triage fallback.

#### 6. Strongly-Typed Decoding (`SystemOneAs`)
Decode straight into a custom struct:
```go
type MyResult struct {
    Billing  typesafe.NoulAnswer   `json:"billing"`
    Category typesafe.ChoiceAnswer `json:"category"`
    Urgency  typesafe.ScoreAnswer  `json:"urgency"`
}
res, err := typesafe.SystemOneAs[MyResult](ctx, client, state, questions)
```

#### 7. Error Handling
SDK errors are strongly typed:
```go
if apiErr, ok := typesafe.AsRateLimitError(err); ok {
    wait, _ := apiErr.RetryAfter()
    time.Sleep(wait)
} else if ptlErr, ok := typesafe.AsPayloadTooLargeError(err); ok {
    // 413 Payload Too Large (>32 MiB)
} else if apiErr, ok := typesafe.AsAPIError(err); ok {
    log.Printf("API error %d: %s (req_id=%s)", apiErr.Status, apiErr.Message, apiErr.RequestID)
}
```

命令行工具 (CLI)

SDK 内置了轻量级 CLI 工具 typesafe:

go install github.com/guchengod/typesafe-sdk-go/cmd/typesafe@latest

使用示例:

export TYPESAFE_API_KEY="ts_live_..."

# 查看可用模型
typesafe models --format text

# 发起问答评估(支持文本与 JSON 输出)
typesafe ask \
  --state '{"body": "发票 #4912 被扣了两次钱。"}' \
  --noul 'billing=这是账单问题吗?' \
  --choice 'queue=billing:财务账单,technical:技术故障,other' \
  --score 'urgency=日常,紧急,严重' \
  --box 'target=图像中的目标物体' \
  --format text

配合 jq 在 CI/CD 流水线中进行自动化质检:

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

错误处理

SDK 提供了完整的强类型错误体系:

resp, err := client.SystemOne(ctx, state, questions)
if err != nil {
	switch {
	case errors.Is(err, context.DeadlineExceeded):
		log.Println("调用超时")
	default:
		if rateLimitErr, ok := typesafe.AsRateLimitError(err); ok {
			wait, _ := rateLimitErr.RetryAfter()
			log.Printf("触发限频,建议等待: %v", wait)
		} else if ptlErr, ok := typesafe.AsPayloadTooLargeError(err); ok {
			log.Printf("请求体超过 32 MiB 上限: %v", ptlErr)
		} else if apiErr, ok := typesafe.AsAPIError(err); ok {
			log.Printf("API 响应错误: status=%d, request_id=%s", apiErr.Status, apiErr.RequestID)
		}
	}
}

文档与示例


参与贡献

欢迎通过 GitHub Issues 与 Pull Requests 参与建设:

go build ./...
go vet ./...
go test ./...                        # 运行单元测试
go test -run TestIntegration ./...   # 运行集成测试(需要设置 TYPESAFE_API_KEY)
go test -bench . -run XXX ./...      # 运行性能基准测试

许可证

基于 MIT 许可证 开源。

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

A success response is held to the same standard: the model, the usage with both token counts, and one answer per question must be present, or the SDK reports an APIResponseValidationError naming the missing field.

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, honoring the retry-after-ms and Retry-After headers for up to a minute, with an optional overall budget. 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"

	// ModelJevLatest is the alias for the latest stable model release (currently jev-1.13.0).
	ModelJevLatest = "jev-latest"

	// ModelJevPreview is the alias for the latest preview release (currently jev-1.14.0).
	ModelJevPreview = "jev-preview"

	// ModelJev114 is the versioned ID for Jev 1.14.
	ModelJev114 = "jev-1.14.0"

	// ModelJev113 is the versioned ID for Jev 1.13.
	ModelJev113 = "jev-1.13.0"

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

	// SDKName is the name of this SDK.
	SDKName = "typesafe-sdk-go"

	// SDKVersion is the current version of this SDK.
	SDKVersion = "0.13.0"

	// MaxErrorBodyLength limits the raw error body length included in error messages.
	MaxErrorBodyLength = 200

	// MaxRequestBodyLimit is the maximum request body size (32 MiB) accepted by the API.
	MaxRequestBodyLimit = 32 * 1024 * 1024

	// MaxResponseBodyLimit limits the maximum response body read (32 MiB) matching API spec.
	MaxResponseBodyLimit = 32 * 1024 * 1024

	// MaxImageSizeLimit is the maximum size (16 MiB) for an individual image file.
	MaxImageSizeLimit = 16 * 1024 * 1024

	// MinCompressionBytes is the minimum request body size (1 KiB) for automatic compression.
	MinCompressionBytes = 1024
)

Client and protocol defaults.

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

API Endpoint paths.

View Source
const (
	// StatusOverloaded is the HTTP 529 status code returned when TypeSafe is temporarily overloaded.
	StatusOverloaded = 529

	// StatusPayloadTooLarge is HTTP 413 Payload Too Large.
	StatusPayloadTooLarge = 413

	// StatusUnsupportedMediaType is HTTP 415 Unsupported Media Type.
	StatusUnsupportedMediaType = 415
)

HTTP Status codes.

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"

	// Danger Zone feature headers.
	HeaderDangerZoneImages      = "TypeSafe-Danger-Zone-Images"
	HeaderDangerZoneLongContext = "TypeSafe-Danger-Zone-Long-Context"

	// Compression headers.
	HeaderContentEncoding = "Content-Encoding"
	EncodingGzip          = "gzip"
	EncodingZstd          = "zstd"
)

Protocol headers.

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", "score", or "box".
	AnswerType() string
}

Answer is a single answer to a single question. The concrete type is NoulAnswer, ChoiceAnswer, ScoreAnswer, or BoxAnswer, 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 Asset added in v0.13.0

type Asset struct {
	// Type is the kind of asset; currently always "image".
	Type string `json:"type,omitzero"`

	// Part is the name of the file part in a multipart request.
	Part string `json:"part,omitzero"`

	// DataBase64 is the standard base64-encoded image content.
	DataBase64 string `json:"data_base64,omitzero"`

	// Format is the image format: "png", "jpg", or "jpeg".
	Format string `json:"format,omitzero"`
}

Asset describes an asset attached to a System One request.

type Assets added in v0.13.0

type Assets map[string]Asset

Assets maps reference strings to their Asset descriptions.

func CollectAssets added in v0.13.0

func CollectAssets(images ...*Image) Assets

CollectAssets returns an Assets map containing all the given images.

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 BoxAnswer added in v0.13.0

type BoxAnswer struct {
	// Type is always "box".
	Type string `json:"type"`

	// Box holds the bounding box edges as fractions from 0 to 1.
	Box BoxCoordinates `json:"box"`

	// Confidence is the statistical certainty of the box from 0 to 1, or nil when the API
	// did not report one.
	Confidence *float64 `json:"confidence"`
}

BoxAnswer is a bounding box locating an object or subject in an image.

See the box primitive (https://docs.typesafe.ai/primitives/box) for details.

func (*BoxAnswer) AnswerType added in v0.13.0

func (a *BoxAnswer) AnswerType() string

AnswerType returns the wire discriminator.

type BoxCoordinates added in v0.13.0

type BoxCoordinates struct {
	// Top is the top edge, as a fraction of image height from the top (0 to 1).
	Top float64 `json:"top"`

	// Bottom is the bottom edge, as a fraction of image height from the top (0 to 1).
	Bottom float64 `json:"bottom"`

	// Left is the left edge, as a fraction of image width from the left (0 to 1).
	Left float64 `json:"left"`

	// Right is the right edge, as a fraction of image width from the left (0 to 1).
	Right float64 `json:"right"`
}

BoxCoordinates holds a bounding box's edges as fractions of an image's height (top, bottom) and width (left, right).

type BoxQuestion added in v0.13.0

type BoxQuestion struct {
	// Type is always "box".
	Type string `json:"type"`

	// Instructions is what to locate, expressed as a string, a JSON object, or an array.
	Instructions any `json:"instructions,omitzero"`
	// contains filtered or unexported fields
}

BoxQuestion locates what its instructions describe in an image.

Box questions find where something is in an image and return bounding box coordinates as fractions of the image's width and height. Requires the TypeSafe-Danger-Zone-Images: true header and the jev-preview / jev-1.14.0 model.

See the box primitive (https://docs.typesafe.ai/primitives/box) for details.

func NewBox added in v0.13.0

func NewBox(instructions any, opts ...QuestionOption) *BoxQuestion

NewBox creates a box question to locate a subject in an image.

typesafe.NewBox("the coffee mug in photo")

func (*BoxQuestion) MarshalJSON added in v0.13.0

func (q *BoxQuestion) MarshalJSON() ([]byte, error)

MarshalJSON encodes the question, including any fields added by WithQuestionField.

func (*BoxQuestion) QuestionType added in v0.13.0

func (q *BoxQuestion) QuestionType() string

QuestionType returns the wire discriminator.

func (*BoxQuestion) Validate added in v0.13.0

func (q *BoxQuestion) Validate(string) error

Validate accepts every box question (box questions take no criteria).

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 field at all, or with more than 255 options.

An empty criteria object is sent rather than refused: the request schema requires the field, not a minimum number of entries, and the official client SDKs make no such check.

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

	// Compression enables automatic request body compression for bodies >= 1 KiB.
	// Defaults to true.
	Compression bool

	// LongContext enables the TypeSafe-Danger-Zone-Long-Context header by default (262k token context).
	LongContext bool

	// Images enables the TypeSafe-Danger-Zone-Images header by default.
	Images bool
}

Config is the resolved client configuration.

type Image added in v0.13.0

type Image struct {
	// Ref is the unique reference string used to refer to this image.
	Ref string

	// Format is the normalized format ("png" or "jpg").
	Format string

	// Data is the raw image file bytes.
	Data []byte

	// Base64 is the base64-encoded representation of Data.
	Base64 string
}

Image represents an image asset that can be referenced in state, instructions, or criteria.

func NewImage added in v0.13.0

func NewImage(data []byte, format string) (*Image, error)

NewImage creates an image asset with an auto-generated unique reference.

Accepted formats are "png", "jpg", and "jpeg". Maximum size is 16 MiB.

func NewImageFromBase64 added in v0.13.0

func NewImageFromBase64(ref, b64, format string) (*Image, error)

NewImageFromBase64 creates an image asset from a base64 string.

func NewImageFromFile added in v0.13.0

func NewImageFromFile(path string) (*Image, error)

NewImageFromFile loads an image from disk and creates an Image asset.

func NewImageWithRef added in v0.13.0

func NewImageWithRef(ref string, data []byte, format string) (*Image, error)

NewImageWithRef creates an image asset with a caller-chosen reference string.

func (*Image) Asset added in v0.13.0

func (img *Image) Asset() Asset

Asset returns the wire Asset definition for this image.

func (*Image) MarshalJSON added in v0.13.0

func (img *Image) MarshalJSON() ([]byte, error)

MarshalJSON encodes the image as its reference string, allowing Image objects to be embedded directly inside state, instructions, or criteria.

func (*Image) String added in v0.13.0

func (img *Image) String() string

String returns the image reference string.

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 WithCompression added in v0.13.0

func WithCompression(enabled bool) Option

WithCompression sets whether request bodies >= 1 KiB are compressed (gzip). Default is true. Pass false to send uncompressed bodies.

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 WithImages added in v0.13.0

func WithImages(enabled bool) Option

WithImages sets whether to send TypeSafe-Danger-Zone-Images: true with every request.

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 WithLongContext added in v0.13.0

func WithLongContext(enabled bool) Option

WithLongContext sets whether to send TypeSafe-Danger-Zone-Long-Context: true with every request.

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 PayloadTooLargeError added in v0.13.0

type PayloadTooLargeError struct{ APIError }

PayloadTooLargeError is an HTTP 413 response.

func AsPayloadTooLargeError added in v0.13.0

func AsPayloadTooLargeError(err error) (*PayloadTooLargeError, bool)

AsPayloadTooLargeError extracts the PayloadTooLargeError when the failure was an HTTP 413.

func (*PayloadTooLargeError) Unwrap added in v0.13.0

func (e *PayloadTooLargeError) 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", "score", or "box".
	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, BoxQuestion, 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, NewScore, and NewBox, 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 no more than 255 countable options.

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 WithAssets added in v0.13.0

func WithAssets(assets any) RequestOption

WithAssets attaches an assets map to the request, containing image assets. It accepts Assets, map[string]Asset, or map[string]any.

func WithCallCompression added in v0.13.0

func WithCallCompression(enabled bool) RequestOption

WithCallCompression overrides body compression 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 WithCallImages added in v0.13.0

func WithCallImages(enabled bool) RequestOption

WithCallImages sets TypeSafe-Danger-Zone-Images: true for a single call.

func WithCallLongContext added in v0.13.0

func WithCallLongContext(enabled bool) RequestOption

WithCallLongContext sets TypeSafe-Danger-Zone-Long-Context: true for a single call.

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

	// MaxRetryAfter caps how long a server retry hint is honored, matching the official client SDKs:
	// a longer hint is ignored in favor of the computed backoff. Zero honors any hint.
	MaxRetryAfter time.Duration

	// 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 an optional 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.
	//
	// The official client SDKs bound each attempt and the number of attempts but apply no overall
	// budget, so the default policy leaves this nil. Set it, for example to new(30*time.Second), when
	// a call must return within a known time.
	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 = new(30 * time.Second) // bound the whole call

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.

The values match the official client SDKs: two retries after the initial attempt, a jittered exponential backoff from 500ms to 5s, retries for 408, 429, and every 5xx and for connection and timeout failures, and server retry hints no longer than a minute. No overall budget is applied.

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) Boxes added in v0.13.0

func (r *SystemOneResponse) Boxes() map[string]*BoxAnswer

Boxes returns the box answers keyed by question name.

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 UnsupportedMediaTypeError added in v0.13.0

type UnsupportedMediaTypeError struct{ APIError }

UnsupportedMediaTypeError is an HTTP 415 response.

func AsUnsupportedMediaTypeError added in v0.13.0

func AsUnsupportedMediaTypeError(err error) (*UnsupportedMediaTypeError, bool)

AsUnsupportedMediaTypeError extracts the UnsupportedMediaTypeError when the failure was an HTTP 415.

func (*UnsupportedMediaTypeError) Unwrap added in v0.13.0

func (e *UnsupportedMediaTypeError) Unwrap() error

Unwrap exposes the embedded APIError to errors.As.

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.
	OutputTokens int `json:"output_tokens"`
}

Usage reports the token counts for a request.

func (Usage) String

func (u Usage) String() string

String renders the token counts.

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