kit

Decision API

Decision models classify, route, score and provide probabilistic gates. They return typed answers with probabilities, not chat or completion text. They are not security authorization guarantees. Enforce access rules in host code.

Discover models

GetDecisionModels returns sorted DecisionModelInfo entries. Model is the provider/model identifier; Provider, ID and Name identify the catalog entry. Wire is systemone, workers-ai, or gateway; an empty wire means Kit does not know how to call that entry. AcceptsImages reports catalog image input, ContextLimit is tokens (zero means unknown), and InputCostPerMTok is USD per million input tokens. Catalog presence does not guarantee credentials, remote availability or a supported wire. Use kit update-models to refresh the catalog; do not hard-code a model count or price.

Standalone or instance

Decide (kit.Decide(ctx, req)) needs no Kit instance. It resolves stored credentials or provider environment variables with default transport settings, without an instance transport wrapper. Kit.Decide uses the instance HTTP transport and TLS setting, and endpoint/API-key overrides when they apply to the requested model. It does not switch the chat model or append a normal agent turn to its session. See authentication and transport tracing.

A decision model cannot serve as an agent chat model. Selecting one with SetModel returns ErrDecisionModelNotChat.

Build a request

DecisionRequest requires a decision Model, nonempty State, and ordered Questions with unique, nonempty IDs. ProviderOptions passes wire-dependent options through where supported (currently the gateway wire); it is not a universal provider configuration contract.

DecisionState accepts Text, JSON-encodable JSON, or both, with optional Images. Images can also supply the state alone. DecisionImage contains MediaType, nonempty Data bytes and optional Filename. PNG and JPEG are supported on image-capable models; Cloudflare and Ollama also accept WebP. Cloudflare permits at most four images. Jev can reject images smaller than about 300 pixels per side. Check model capability first. Image representation differs by wire: multipart assets, data URLs, inline base64 or gateway state parts; identical wire bodies are not promised.

DecisionQuestionType has DecisionBoolean, DecisionChoice and DecisionScore. Build a DecisionQuestion with BooleanQuestion, ChoiceQuestion and ScoreQuestion. Boolean questions need instructions or True/False descriptions. A choice has 2–255 uniquely named DecisionOption values, with optional descriptions. Scores have 2–10 nonempty ordered levels, lowest first. Non-gateway wires accept at most 64 questions. Validation occurs before sending.

The following complete example is also compile-checked in pkg/kit/decide_example_test.go. Normal tests do not execute it or make provider requests.

package kit_test

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/mark3labs/kit/pkg/kit"
)

// ExampleDecide demonstrates all three question kinds. It needs provider
// credentials and network access, so normal tests compile but do not run it.
func ExampleDecide() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	result, err := kit.Decide(ctx, kit.DecisionRequest{
		Model: "openrouter/typesafe/jev-1.14", // Discover current models with GetDecisionModels.
		State: kit.DecisionState{Text: "My card was charged twice for one order."},
		Questions: []kit.DecisionQuestion{
			kit.BooleanQuestion("refund", "Does the customer request a refund?"),
			kit.ChoiceQuestion("team", "Which team should handle this ticket?",
				kit.DecisionOption{Name: "billing", Description: "Payments and refunds"},
				kit.DecisionOption{Name: "technical"}),
			kit.ScoreQuestion("urgency", "How urgent is this ticket?", "low", "medium", "high"),
		},
	})
	if err != nil {
		if apiErr, ok := (errors.AsType[*kit.DecisionAPIError])(err); ok {
			fmt.Println("Provider HTTP status:", apiErr.StatusCode)
		} else if errors.Is(err, kit.ErrInvalidDecisionRequest) {
			fmt.Println("Correct the request before sending again")
		}
		return
	}
	refund, err := result.Boolean("refund")
	if err != nil {
		fmt.Println("Refund answer unavailable")
		return
	}
	team, err := result.Choice("team")
	if err != nil {
		fmt.Println("Team answer unavailable")
		return
	}
	urgency, err := result.Score("urgency")
	if err != nil {
		fmt.Println("Urgency answer unavailable")
		return
	}
	fmt.Println(refund.Probability, team.Choice, urgency.Score)
	fmt.Println("Input tokens:", result.Usage.InputTokens)
}

Read answers by ID

DecisionResult contains ordered Answers, serving Model, Usage and provider-specific Metadata. Use question IDs, not positions, to retrieve answers. Answer(id string) (DecisionAnswer, bool) reports presence. Boolean(id string) (DecisionAnswer, error), Choice(id string) (DecisionAnswer, error) and Score(id string) (DecisionAnswer, error) also check type and refusal. Always check the error before using a zero value.

DecisionAnswer contains Probability, which is P(true) only for boolean answers and remains zero for choice and score answers. Use Prob(name) for a named option or level. Choice is the selected name. Score is the expected zero-based level, possibly fractional; Level is the most probable level when a distribution exists (otherwise rounded score). Distribution holds DecisionProbability values in question order. Prob(name) returns (float64, bool); absence is not zero probability. Confidence, when present, measures distribution concentration, not probability of correctness. Refused marks refusal. DecisionUsage contains input and output token counts. Do not assume all providers return distributions, confidence, model names, usage detail or metadata.

Errors and cancellation

Use errors.Is for wrapped sentinels:

Sentinel Action
ErrNotDecisionModel Select a decision catalog entry, not a chat model.
ErrDecisionModelNotChat Use Decide instead of chat/SetModel.
ErrUnknownDecisionWire Select an entry with a supported wire.
ErrInvalidDecisionRequest Correct validation failures; nothing was sent.
ErrInvalidDecisionResponse Treat mismatched, missing or malformed answers as failure.
ErrDecisionRefused A typed accessor found a refused answer; use an explicit host fallback.
ErrNoDecisionAnswer Check the question ID.
ErrWrongDecisionAnswerType Use the accessor matching the question kind.

For example, if errors.Is(err, kit.ErrDecisionRefused) { /* host fallback */ } handles accessor refusal. The example uses errors.AsType to inspect the StatusCode of DecisionAPIError. Its Message and raw Body can contain sensitive provider data: do not log them or request bodies/credentials by default. Provider errors are also classified through ClassifyProviderError.

Supply a context deadline. Each HTTP attempt is bounded to two minutes. There are at most three attempts (initial plus two retries), for network/read failures and HTTP 429, 529, 500, 502, 503 and 504. Backoff is one then two seconds; a positive integer-seconds Retry-After replaces it, capped at 30 seconds. Parent cancellation stops requests and waits. Validation and response decoding are not retried. A retry can incur additional provider cost; do not assume exactly-once remote processing.

These limits are independent of agent-loop RetryPolicy and step resume. Decision calls do not resume chat steps or execute local tools. See agent recovery for that separate contract, and the API index for the full public surface.