jev

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 21 Imported by: 0

README

jevgo

Go Reference License

A Go client for TypeSafe AI's System One API and its flagship model, Jev. Send a state and typed questions; get typed answers and probabilities your code can act on directly. It covers the same API surface as the official Python and TypeScript SDKs with the same defaults, written the way a Go library should be: functional options, context cancellation, sentinel errors, log/slog, and no dependencies outside the standard library. See Compatibility for where behavior intentionally differs.

Optional Langfuse instrumentation ships as a separate module, contrib/langfuse, built on go-langfuse. This is a community client; it is not affiliated with TypeSafe.

Install

go get github.com/fgn/jevgo

Pass the API key explicitly, or set TYPESAFE_API_KEY in the environment and call jev.NewClient() with no options:

client, err := jev.NewClient(jev.WithAPIKey(os.Getenv("MY_TYPESAFE_KEY")))

A complete program:

package main

import (
	"context"
	"fmt"
	"log"

	jev "github.com/fgn/jevgo"
)

func main() {
	client, err := jev.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	resp, err := client.SystemOne(context.Background(), jev.Request{
		State: "I was charged twice. Please fix this ASAP.",
		Questions: jev.Questions{
			"urgent": jev.Noul{Instructions: "Does this convey urgency?"},
			"department": jev.Choice{
				Instructions: "Which team should handle this?",
				Criteria:     map[string]any{"billing": nil, "technical": nil, "other": nil},
			},
			"frustration": jev.Score{
				Instructions: "How frustrated is the customer?",
				Criteria:     []string{"Calm", "Frustrated", "Very angry"},
			},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	if department, ok := resp.Choice("department"); ok {
		fmt.Println(department.Choice, department.Confidence)
	}
}

Questions and answers

The three question types map to three answer types. Answers come back under the names you chose, as a sealed interface you can type switch on, or through typed getters.

Question Answer What you get
jev.Noul jev.NoulAnswer probability of yes in Noul
jev.Choice jev.ChoiceAnswer selected option in Choice, Probabilities per option, Confidence
jev.Score jev.ScoreAnswer weighted position in Score, Probabilities and Legend per level, Confidence, Level() for the most likely level
for name, answer := range resp.Answers {
	switch a := answer.(type) {
	case jev.NoulAnswer:
		fmt.Println(name, a.Noul)
	case jev.ChoiceAnswer:
		fmt.Println(name, a.Choice, a.Confidence)
	case jev.ScoreAnswer:
		fmt.Println(name, a.Score, a.Legend[a.Level()]) // Score is a weighted average, not a level
	case jev.UnknownAnswer: // an answer kind newer than this SDK; a.Raw holds the JSON
	}
}

State, instructions, and criteria accept strings or structured JSON: maps, slices, or structs with json tags, so a []string of levels or a map[string]string of options works as is. jev.RawQuestion sends a question verbatim for fields this version does not model, and Request.Extra adds top-level request fields. Response.RawBody keeps the complete response. Responses are validated against the API contract and the questions as sent, including Request.Extra overrides: a missing answer, an option or level that was not asked for, a null probability, a distribution that does not sum to one, or a choice that is not the most probable option is a *jev.ResponseValidationError rather than a silent zero. Sums and weighted scores allow for the API's rounding.

Request and Response are not the wire format; the client encodes and decodes them. Individual questions and answers marshal to their wire JSON.

Configuration

Explicit options win over environment variables, which win over defaults. An empty string means "not set". Every option is also accepted per call as an override, and options are safe to build once and reuse across goroutines.

Option Environment Default
WithAPIKey TYPESAFE_API_KEY required
WithBaseURL TYPESAFE_BASE_URL https://api.typesafe.ai
WithDefaultModel TYPESAFE_DEFAULT_MODEL jev-latest
WithLogger TYPESAFE_LOG_LEVEL no logging
WithTimeout (per attempt) 10s
WithMaxRetries, WithRetryPolicy 2 retries, 500ms to 5s backoff, honors Retry-After
WithHTTPClient, WithHeader, WithTracer
policy := jev.DefaultRetryPolicy()
policy.TotalTimeout = 30 * time.Second // deadline for attempts and delays together

client, err := jev.NewClient(
	jev.WithRetryPolicy(policy),
	jev.WithLogger(slog.Default()),
)
resp, err := client.SystemOne(ctx, req, jev.WithTimeout(3*time.Second), jev.WithMaxRetries(4))

WithTimeout bounds each attempt; TotalTimeout bounds the whole call and reports as ErrTimeout when it expires. The caller's context deadline always applies as well.

Errors

resp, err := client.SystemOne(ctx, req)
switch {
case errors.Is(err, jev.ErrRateLimit):
	// HTTP 429 after retries; also ErrAuthentication, ErrUnprocessableEntity,
	// ErrOverloaded (529), ErrInternalServer, ...
case errors.Is(err, jev.ErrTimeout):
	// per-attempt, total, or transport timeout after retries; check this before
	// context.DeadlineExceeded, which SDK timeouts also wrap
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
	// the caller's context
case errors.Is(err, jev.ErrInvalidRequest):
	// rejected before sending: no questions, a Score without levels, a typed nil question, ...
}

var apiErr *jev.APIError
if errors.As(err, &apiErr) {
	log.Println(apiErr.StatusCode, apiErr.Message, apiErr.RequestID, apiErr.Attempts)
}

A successful response that does not match the API contract is a *jev.ResponseValidationError naming the offending field, including a body over 16 MiB (ErrResponseTooLarge), which is never retried. API, connection, and validation errors and every response carry the request ID and the number of HTTP attempts made; errors raised before a request is sent, and the caller's own context errors, do not.

Credential headers are redacted from logs. Server messages in APIError and debug-level bodies may contain whatever the server or your state included, so treat them as sensitive.

Instrumentation

jev.Tracer observes every SystemOne call that passes local validation, with the wire body and effective model at start and the decoded response, error, and attempt count at end. The context it returns is used for the HTTP requests, so spans parent correctly. The contrib/langfuse module records one Langfuse generation per call with model, input, output, token usage, request ID, and attempt count:

client, err := jev.NewClient(jev.WithTracer(jevlangfuse.NewTracer(lf)))

For transport-level instrumentation, pass an *http.Client with your own RoundTripper through WithHTTPClient.

Compatibility

Behavior follows the API reference and the OpenAPI schema, and defaults match the official SDKs. Known differences:

Behavior This client Python TypeScript
Score with one level accepted (OpenAPI minItems: 1) accepted rejected
Missing or null response fields validation error validation error, except usage counters passed through
Unknown answer kinds UnknownAnswer with raw JSON skipped passed through
Retry-After above 60s falls back to backoff honored, 30s call budget falls back to backoff
Total call budget off by default 30s by default none
Per-call header with the same name replaces replaces replaces
Model release_date string as sent string string

Examples

Development

task        # format, lint, test
task test:live   # exercises the real API with TYPESAFE_API_KEY

Behavior follows the TypeSafe API reference and the OpenAPI schema; defaults for timeouts, retries, and headers match the official SDKs, and the compatibility table lists the differences.

Documentation

Overview

Package jev is a Go client for the TypeSafe AI System One API and its flagship model, Jev: send a state and typed questions, get typed answers with probabilities.

client, err := jev.NewClient() // reads TYPESAFE_API_KEY
...
resp, err := client.SystemOne(ctx, jev.Request{
	State: "I was charged twice. Please fix this ASAP.",
	Questions: jev.Questions{
		"urgent":     jev.Noul{Instructions: "Does this convey urgency?"},
		"department": jev.Choice{
			Instructions: "Which team should handle this?",
			Criteria:     map[string]any{"billing": nil, "technical": nil, "other": nil},
		},
		"frustration": jev.Score{
			Instructions: "How frustrated is the customer?",
			Criteria:     []string{"Calm", "Frustrated", "Very angry"},
		},
	},
})
...
if department, ok := resp.Choice("department"); ok {
	fmt.Println(department.Choice, department.Confidence)
}

Answers are a sealed interface: type switch on NoulAnswer, ChoiceAnswer, ScoreAnswer, and UnknownAnswer, or use the typed getters on Response. See the README for configuration, errors, retries, and instrumentation.

Index

Examples

Constants

View Source
const (
	DefaultBaseURL = "https://api.typesafe.ai"
	DefaultModel   = "jev-latest"
	// DefaultTimeout applies to each HTTP attempt; see
	// [RetryPolicy.TotalTimeout] for a whole-call deadline.
	DefaultTimeout = 10 * time.Second
)

Defaults, matching the official TypeSafe SDKs.

View Source
const (
	EnvAPIKey       = "TYPESAFE_API_KEY"
	EnvBaseURL      = "TYPESAFE_BASE_URL"
	EnvDefaultModel = "TYPESAFE_DEFAULT_MODEL"
	EnvLogLevel     = "TYPESAFE_LOG_LEVEL"
)

Environment variables read when the matching option is not set. Blank values are ignored.

View Source
const Version = "0.3.0"

Version is sent in the User-Agent and X-Typesafe-Sdk headers.

Variables

View Source
var (
	ErrBadRequest          = errors.New("jev: bad request")           // HTTP 400
	ErrAuthentication      = errors.New("jev: authentication failed") // HTTP 401
	ErrPermissionDenied    = errors.New("jev: permission denied")     // HTTP 403
	ErrNotFound            = errors.New("jev: not found")             // HTTP 404
	ErrUnprocessableEntity = errors.New("jev: unprocessable entity")  // HTTP 422
	ErrRateLimit           = errors.New("jev: rate limit exceeded")   // HTTP 429
	ErrOverloaded          = errors.New("jev: service overloaded")    // HTTP 529
	ErrInternalServer      = errors.New("jev: internal server error") // HTTP 5xx, including 529
)

Status sentinels matched by *APIError through errors.Is.

View Source
var (
	// ErrConnection matches every ConnectionError.
	ErrConnection = errors.New("jev: connection error")
	// ErrTimeout matches a ConnectionError caused by a timeout: the
	// per-attempt timeout, [RetryPolicy.TotalTimeout], or the transport's own.
	ErrTimeout = errors.New("jev: request timed out")
)

Transport sentinels matched by *ConnectionError through errors.Is.

View Source
var ErrInvalidConfig = errors.New("jev: invalid configuration")

ErrInvalidConfig is wrapped by configuration errors.

View Source
var ErrInvalidRequest = errors.New("jev: invalid request")

ErrInvalidRequest is wrapped by errors for requests rejected before they are sent.

View Source
var ErrResponseTooLarge = errors.New("jev: response body exceeds 16 MiB")

ErrResponseTooLarge is the Err of a *ResponseValidationError for a response body over 16 MiB. It is never retried.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	StatusCode int
	Method     string
	URL        string
	Header     http.Header
	// Body is the raw response body, or nil when empty.
	Body []byte
	// Message is the server's message extracted from Body, or the body
	// itself, truncated to 200 characters. It can contain anything the
	// server reflected, including request data.
	Message string
	// RequestID is the X-Typesafe-Request-Id header, or empty when absent.
	RequestID string
	// Attempts is the number of HTTP attempts made, including retries.
	Attempts int
}

APIError is an unsuccessful HTTP response, returned after any retries. It matches the status sentinels through errors.Is:

if errors.Is(err, jev.ErrRateLimit) { ... }
Example
package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"

	jev "github.com/fgn/jevgo"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Retry-After", "30")
		w.WriteHeader(http.StatusTooManyRequests)
		fmt.Fprint(w, `{"error":"rate limit exceeded"}`)
	}))
	defer server.Close()

	client, err := jev.NewClient(jev.WithAPIKey("key"), jev.WithBaseURL(server.URL), jev.WithMaxRetries(0))
	if err != nil {
		panic(err)
	}
	_, err = client.SystemOne(context.Background(), jev.Request{
		State:     "hello",
		Questions: jev.Questions{"greeting": jev.Noul{Instructions: "Is this a greeting?"}},
	})
	var apiErr *jev.APIError
	if errors.As(err, &apiErr) {
		retryAfter, _ := apiErr.RetryAfter()
		fmt.Println(errors.Is(err, jev.ErrRateLimit), apiErr.StatusCode, apiErr.Message, retryAfter)
	}
}
Output:
true 429 rate limit exceeded 30s

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Is

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

Is reports whether target is the status sentinel for this error.

func (*APIError) RetryAfter

func (e *APIError) RetryAfter() (time.Duration, bool)

RetryAfter returns the delay requested by the Retry-After-Ms or Retry-After header, when present and valid.

type Answer

type Answer interface {
	json.Marshaler
	// contains filtered or unexported methods
}

Answer is one of NoulAnswer, ChoiceAnswer, ScoreAnswer, or UnknownAnswer.

type Choice

type Choice struct {
	// Instructions is a string, or a JSON object or array.
	Instructions any
	// Criteria is a JSON object mapping each option to its description, such
	// as a map[string]string or map[string]any. A nil description leaves the
	// option undescribed.
	Criteria any
}

Choice selects one option from a defined set.

func (Choice) MarshalJSON

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

MarshalJSON encodes the question in the API wire format.

type ChoiceAnswer

type ChoiceAnswer struct {
	// Choice is the highest-probability option.
	Choice string `json:"choice"`
	// Confidence is the model's certainty in Choice, from 0 to 1.
	Confidence float64 `json:"confidence"`
	// Probabilities maps every option to its probability.
	Probabilities map[string]float64 `json:"probabilities"`
}

ChoiceAnswer answers a Choice.

func (ChoiceAnswer) MarshalJSON

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

MarshalJSON encodes the answer in the API wire format.

type Client

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

Client calls the TypeSafe API. Create it with NewClient; it is safe for concurrent use.

func NewClient

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

NewClient creates a client. Explicit options take precedence over environment variables, which take precedence over defaults. The API key is required.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the configured API root without a trailing slash.

func (*Client) DefaultModel

func (c *Client) DefaultModel() string

DefaultModel returns the model used when a request does not name one.

func (*Client) ListModels

func (c *Client) ListModels(ctx context.Context, opts ...Option) (*ModelsResponse, error)

ListModels returns the models available to the account.

func (*Client) SystemOne

func (c *Client) SystemOne(ctx context.Context, req Request, opts ...Option) (*Response, error)

SystemOne evaluates req.State against req.Questions and returns one answer per question under the same names.

Errors are *APIError for unsuccessful responses, *ConnectionError for transport failures and timeouts, both after retries; *ResponseValidationError for a successful response that does not match the contract; an error wrapping ErrInvalidRequest for requests rejected before sending; and the context's error when ctx is done.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	jev "github.com/fgn/jevgo"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, `{"model":"jev-1.13","answers":{
			"department":{"type":"choice","choice":"billing","confidence":0.9,"probabilities":{"billing":0.9,"other":0.1}},
			"frustration":{"type":"score","score":1.4,"confidence":0.7,
				"legend":{"0":"Calm","1":"Frustrated","2":"Very angry"},"probabilities":{"0":0.1,"1":0.4,"2":0.5}}},
			"usage":{"input_tokens":100,"output_tokens":10}}`)
	}))
	defer server.Close()

	client, err := jev.NewClient(jev.WithAPIKey("key"), jev.WithBaseURL(server.URL))
	if err != nil {
		panic(err)
	}
	resp, err := client.SystemOne(context.Background(), jev.Request{
		State: "I was charged twice. Please fix this ASAP.",
		Questions: jev.Questions{
			"department": jev.Choice{
				Instructions: "Which team should handle this?",
				Criteria:     map[string]any{"billing": nil, "other": nil},
			},
			"frustration": jev.Score{
				Instructions: "How frustrated is the customer?",
				Criteria:     []string{"Calm", "Frustrated", "Very angry"},
			},
		},
	})
	if err != nil {
		panic(err)
	}
	department, _ := resp.Choice("department")
	frustration, _ := resp.Score("frustration")
	fmt.Println(department.Choice, department.Confidence)
	fmt.Println(frustration.Score, frustration.Legend[frustration.Level()])
}
Output:
billing 0.9
1.4 Very angry

type ConnectionError

type ConnectionError struct {
	Method string
	URL    string
	// Elapsed is how long the failed attempt ran.
	Elapsed time.Duration
	// Timeout is the SDK limit that elapsed: the per-attempt timeout or
	// [RetryPolicy.TotalTimeout]. It is zero when the failure was not an SDK
	// timeout, including timeouts raised by the transport itself.
	Timeout time.Duration
	// StatusCode, Header, and RequestID are set when the response headers
	// arrived before the body failed.
	StatusCode int
	Header     http.Header
	RequestID  string
	Attempts   int
	Err        error
}

ConnectionError is a request that failed without a complete HTTP response, returned after any retries. It matches ErrConnection, and ErrTimeout when the failure was a timeout.

func (*ConnectionError) Error

func (e *ConnectionError) Error() string

func (*ConnectionError) Is

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

Is reports whether target is ErrConnection or, for a timeout, ErrTimeout.

func (*ConnectionError) Unwrap

func (e *ConnectionError) Unwrap() error

type Model

type Model struct {
	// Name is the model name or alias to send as [Request.Model].
	Name        string `json:"name"`
	Description string `json:"description"`
	// ReleaseDate is kept as sent: the API documents YYYY-MM-DD and has
	// returned RFC 3339 timestamps.
	ReleaseDate string `json:"release_date"`
}

Model describes an available model.

type ModelsResponse

type ModelsResponse struct {
	ResponseMetadata

	Models []Model
}

ModelsResponse is the result of Client.ListModels.

type Noul

type Noul struct {
	// Instructions is a string, or a JSON object or array.
	Instructions any
	// Criteria optionally describes what yes and no mean.
	Criteria *NoulCriteria
}

Noul asks a yes/no question. The answer is the probability of yes.

func (Noul) MarshalJSON

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

MarshalJSON encodes the question in the API wire format.

type NoulAnswer

type NoulAnswer struct {
	// Noul is the probability of yes, from 0 to 1.
	Noul float64 `json:"noul"`
}

NoulAnswer answers a Noul.

func (NoulAnswer) MarshalJSON

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

MarshalJSON encodes the answer in the API wire format.

type NoulCriteria

type NoulCriteria struct {
	True  any `json:"true,omitempty"`
	False any `json:"false,omitempty"`
}

NoulCriteria describes the outcomes of a Noul; nil fields are omitted.

type Option

type Option func(*config) error

Option configures a Client. Options apply in order, last wins, and are also accepted per call by Client.SystemOne and Client.ListModels as overrides for that call. An empty string or nil value means "not set" and falls back to the environment or the default.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API key. Defaults to TYPESAFE_API_KEY.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets the API root, for example a proxy. It must be an http or https URL without credentials, query, or fragment. Defaults to TYPESAFE_BASE_URL, then DefaultBaseURL.

func WithDefaultModel

func WithDefaultModel(model string) Option

WithDefaultModel sets the model used when Request.Model is empty. Defaults to TYPESAFE_DEFAULT_MODEL, then DefaultModel.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets the HTTP client, which is where to install a custom or instrumented transport. Defaults to http.DefaultClient. Per-attempt timeouts are applied through the request context; a Timeout on the client also applies and is reported as a transport timeout.

func WithHeader

func WithHeader(key, value string) Option

WithHeader sets a header sent with every request, replacing any earlier value. Authorization, Accept, Content-Type, and the SDK identification headers cannot be overridden.

func WithHeaders

func WithHeaders(headers http.Header) Option

WithHeaders sets headers sent with every request; each key replaces any earlier values for that key.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger: one record per HTTP attempt at slog.LevelInfo, and headers and bodies at slog.LevelDebug. Credential headers are redacted; bodies, which contain your state and answers, are not. Without a logger, TYPESAFE_LOG_LEVEL (debug, info, warn, error, or off) selects a level on slog.Default; unset means no logging.

func WithMaxRetries added in v0.2.0

func WithMaxRetries(n int) Option

WithMaxRetries changes only the retry count, keeping the rest of the current policy. 0 disables retries.

func WithRetryPolicy

func WithRetryPolicy(policy RetryPolicy) Option

WithRetryPolicy replaces the retry policy.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the timeout for each HTTP attempt, including reading the response body. Defaults to DefaultTimeout.

func WithTracer

func WithTracer(tracer Tracer) Option

WithTracer sets the Tracer observing SystemOne calls; nil disables it.

type Question

type Question interface {
	json.Marshaler
	// contains filtered or unexported methods
}

Question is one of Noul, Choice, Score, or RawQuestion.

type Questions

type Questions map[string]Question

Questions maps question names to questions.

type RawQuestion

type RawQuestion map[string]any

RawQuestion is sent as-is, for fields or kinds this SDK version does not model. It must contain a non-empty string "type"; known kinds are validated like their typed forms.

func (RawQuestion) MarshalJSON

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

MarshalJSON encodes the underlying map.

type Request

type Request struct {
	// State is the content every question refers to: a string, or a JSON
	// object or array such as a map, slice, or struct with json tags.
	State any
	// Questions is the nonempty set of named questions. Answers are returned
	// under the same names.
	Questions Questions
	// Model overrides the client's default model when non-empty.
	Model string
	// Extra adds top-level fields this SDK version does not model. Keys are
	// merged last-write-wins after validation, so they can replace state,
	// model, and questions on the wire.
	Extra map[string]any
}

Request is the input to Client.SystemOne. It is not the wire body; SystemOne validates and encodes it.

type Response

type Response struct {
	ResponseMetadata

	// Model is the model that answered the request.
	Model string
	// Answers holds one answer per question under the question's name.
	Answers map[string]Answer
	Usage   Usage
}

Response is the result of Client.SystemOne.

func (*Response) Choice

func (r *Response) Choice(name string) (ChoiceAnswer, bool)

Choice returns the answer to the named Choice question.

func (*Response) Choices

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

Choices returns every ChoiceAnswer by question name.

func (*Response) Noul

func (r *Response) Noul(name string) (NoulAnswer, bool)

Noul returns the answer to the named Noul question.

func (*Response) Nouls

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

Nouls returns every NoulAnswer by question name.

func (*Response) Score

func (r *Response) Score(name string) (ScoreAnswer, bool)

Score returns the answer to the named Score question.

func (*Response) Scores

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

Scores returns every ScoreAnswer by question name.

type ResponseMetadata

type ResponseMetadata struct {
	// RequestID is the X-Typesafe-Request-Id header, or empty when absent.
	RequestID  string
	StatusCode int
	Header     http.Header
	// RawBody is the complete response body, including answers of kinds this
	// SDK version does not model.
	RawBody json.RawMessage
	// Attempts is the number of HTTP attempts made, including retries.
	Attempts int
}

ResponseMetadata carries transport details of a successful response.

type ResponseValidationError

type ResponseValidationError struct {
	StatusCode int
	Method     string
	URL        string
	// Path is the dotted path of the offending field, such as
	// "answers.tone.confidence".
	Path      string
	Header    http.Header
	Body      json.RawMessage
	RequestID string
	Attempts  int
	Err       error
}

ResponseValidationError is a successful HTTP response whose body does not match the API contract or the questions asked.

func (*ResponseValidationError) Error

func (e *ResponseValidationError) Error() string

func (*ResponseValidationError) Unwrap

func (e *ResponseValidationError) Unwrap() error

type RetryPolicy

type RetryPolicy struct {
	// MaxRetries is the number of retries after the initial attempt.
	// Default: 2.
	MaxRetries int
	// InitialBackoff is the first backoff delay, doubled each retry up to
	// MaxBackoff. Default: 500ms.
	InitialBackoff time.Duration
	// MaxBackoff caps the backoff delay. Default: 5s.
	MaxBackoff time.Duration
	// Jitter is the fraction of each backoff delay randomly subtracted, from
	// 0 to 1. Default: 0.25.
	Jitter float64
	// Statuses lists the HTTP status codes that are retried. Default: 408,
	// 429, and 500 through 599.
	Statuses []int
	// RespectRetryAfter honors Retry-After-Ms and Retry-After response headers
	// up to MaxRetryAfter; longer delays fall back to backoff. Default: true.
	RespectRetryAfter bool
	// MaxRetryAfter is the longest server-requested delay honored. Default: 60s.
	MaxRetryAfter time.Duration
	// ConnectionErrors retries connection failures, including interrupted
	// response bodies. Default: true.
	ConnectionErrors bool
	// Timeouts retries attempts that timed out. Default: true.
	Timeouts bool
	// TotalTimeout is a deadline for the whole call, attempts and delays
	// included. When it expires during an attempt the call fails with a
	// [*ConnectionError] matching [ErrTimeout]; a retry whose delay would
	// reach it is skipped and the last error returned. Zero disables it; the
	// caller's context deadline always applies. Default: 0.
	TotalTimeout time.Duration
	// ShouldRetry, when set, is consulted for API and connection errors the
	// rules above do not retry. It never sees context errors or response
	// validation errors, which are not retried.
	ShouldRetry func(error) bool
}

RetryPolicy controls how failed attempts are retried. Start from DefaultRetryPolicy and adjust fields, or use WithMaxRetries; a zero RetryPolicy disables retries.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns the SDK default policy, which matches the official TypeSafe SDKs.

type Score

type Score struct {
	// Instructions is a string, or a JSON object or array.
	Instructions any
	// Criteria is a nonempty JSON array of level descriptions from lowest to
	// highest, such as a []string or []any.
	Criteria any
}

Score rates the state against ordered levels. The answer is a probability-weighted position along them.

func (Score) MarshalJSON

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

MarshalJSON encodes the question in the API wire format.

type ScoreAnswer

type ScoreAnswer struct {
	// Score is the probability-weighted position along the levels, from 0 to
	// the highest level. It usually falls between levels; see [ScoreAnswer.Level].
	Score float64 `json:"score"`
	// Confidence is the model's certainty in Score, from 0 to 1.
	Confidence float64 `json:"confidence"`
	// Legend maps each level to the description sent in the request.
	Legend map[int]any `json:"legend"`
	// Probabilities maps every level to its probability.
	Probabilities map[int]float64 `json:"probabilities"`
}

ScoreAnswer answers a Score.

func (ScoreAnswer) Level added in v0.2.0

func (a ScoreAnswer) Level() int

Level returns the highest-probability level, the lowest on a tie. Use it instead of truncating Score, which is a weighted average.

func (ScoreAnswer) MarshalJSON

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

MarshalJSON encodes the answer in the API wire format.

type SystemOneEndData

type SystemOneEndData struct {
	// Response is nil when Err is non-nil.
	Response *Response
	Err      error
	// Attempts is the number of HTTP attempts made; zero when none was sent.
	Attempts int
}

SystemOneEndData describes a completed call.

type SystemOneStartData

type SystemOneStartData struct {
	Request *Request
	// Model is the model on the wire, after [Request.Extra] is applied.
	Model string
	// Body is the encoded wire request.
	Body json.RawMessage
}

SystemOneStartData describes a call about to be sent.

type Tracer

type Tracer interface {
	TraceSystemOneStart(ctx context.Context, data SystemOneStartData) context.Context
	TraceSystemOneEnd(ctx context.Context, data SystemOneEndData)
}

Tracer observes SystemOne calls that passed local validation. The context returned by TraceSystemOneStart is used for every HTTP attempt and passed to TraceSystemOneEnd, which runs exactly once per traced call.

Implementations must be safe for concurrent use and must not modify the data they receive.

func MultiTracer

func MultiTracer(tracers ...Tracer) Tracer

MultiTracer composes tracers. Start contexts chain in order, so each tracer sees values set by the ones before it; End runs in reverse order with the context returned by the last Start. A tracer listed more than once must cope with being started twice under the same context.

type UnknownAnswer

type UnknownAnswer struct {
	Type string
	// Raw is the complete answer object.
	Raw json.RawMessage
}

UnknownAnswer is an answer of a kind this SDK version does not model.

func (UnknownAnswer) MarshalJSON

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

MarshalJSON returns the raw answer object.

type Usage

type Usage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
}

Usage is the token usage for a request.

Directories

Path Synopsis
contrib
langfuse module
examples
basic command
Command basic asks the three question types about a support ticket.
Command basic asks the three question types about a support ticket.
structured command
Command structured evaluates an order against a return policy using structured state, instructions, and criteria, and shows how to type switch over answers.
Command structured evaluates an order against a return policy using structured state, instructions, and criteria, and shows how to type switch over answers.

Jump to

Keyboard shortcuts

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