decide

package
v0.2.42 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MPL-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package decide is the chassis-owned `ai://decide` exec dispatch surface.

Where ai://chat turns context into generated language and ai://embed turns content into a vector, ai://decide turns state plus bounded, typed questions into probabilistic judgments:

noul    a proposition           → P(true)
choice  one of the given names  → the name, plus a distribution over names
score   an ordered rubric       → an interpolated score, plus a distribution

The answer space is always supplied by the caller, so a backend can never return an option that was not offered.

**Judgments, not policy.** decide returns probabilities and nothing else. Thresholds, fallbacks, allow/deny and escalation belong to the stack, in ordinary WHEN clauses over the result. There is deliberately no threshold parameter anywhere in this package.

Like chat and embed it is a thin registry: backends self-register via init() in their subpackages, and the chassis activates one with a blank import. The contract types below name no provider; a backend translates them to its own wire shape (e.g. `noul` may travel as `boolean`).

**Boundary of trust.** Same as chat and embed: RequiredSecrets() declares the standardized secret names a backend needs; the ExecAI handler materializes them through the per-tenant store (with the optional env fallback); cleartext rides only in the *secrets.SecretBag passed to Decide.

Index

Constants

View Source
const (
	TypeNoul   = "noul"
	TypeChoice = "choice"
	TypeScore  = "score"
)

Question types. These are the provider-independent names; a backend maps them onto its wire vocabulary.

Variables

This section is empty.

Functions

func Normalize

func Normalize(req Request, resp *Response) error

Normalize enforces the answer contract on a backend's response, in place:

  • every question has exactly one answer, of the type it was asked as;
  • a choice is one of the offered names, and its Probability is the distribution's value for that name;
  • a score lies on the level-index scale, 0 … len(Levels)-1;
  • every probability, and a reported confidence, is a finite number in [0, 1];
  • distributions keep only offered names / level indices;
  • answers come back in question order, and nothing else survives.

Any violation fails the whole call: a stack never sees a partial answer set, because a missing path reads as zero in a WHEN comparison.

func ParseState

func ParseState(raw gjson.Result) (json.RawMessage, error)

ParseState checks that raw (a JSON value) is usable as decision state: a non-empty string, an object, or an array. It returns the value unchanged.

func Register

func Register(name string, c Constructor)

Register adds a backend constructor. Called from a backend package's init(); the chassis activates a backend with a blank import.

Re-registering an existing name overwrites the constructor (test support); registration order is preserved on first-registration only.

func Registered

func Registered() []string

Registered returns the names of currently registered backends, sorted.

Types

type Answer

type Answer struct {
	Key           string
	Type          string
	Probability   *float64
	Choice        string
	Score         *float64
	Probabilities map[string]float64
	Confidence    *float64
}

Answer is one question's typed judgment. Which fields are meaningful depends on Type; after Normalize they are:

noul    Probability = P(true)
choice  Choice, Probability = P(Choice), Probabilities by option name
score   Score, Probabilities by level index ("0" = lowest)

Score is interpolated on the level-index scale: 0 is the lowest level and len(Levels)-1 the highest. A score answer carries no Probability — the distribution and the score describe different things.

Confidence is the provider's own confidence in the answer, carried only when the provider reports one (Jev reports it for choice and score, not noul). It is not a probability and is never derived from one.

type Backend

type Backend interface {
	// Name returns the registered name (must match the Register key).
	Name() string

	// DefaultModel is the model used when a request omits WITH model.
	DefaultModel() string

	// RequiredSecrets are the standardized secret names this backend needs.
	RequiredSecrets() []string

	// Decide asks every question in req against req.State in one call. It
	// translates the provider's answers into Response and returns them as
	// reported; Normalize (called by the handler) enforces the contract —
	// every question answered, choices from the offered set, probabilities
	// in range. The bag carries cleartext for every name in
	// RequiredSecrets(); implementations keep it in local variables only.
	Decide(ctx context.Context, req Request, bag *secrets.SecretBag) (Response, error)
}

Backend is the chassis-facing interface every decision backend implements.

Lifecycle: backends register by name via Register() in an init(); the chassis resolves one per ai://decide EXEC. Decide must be safe for concurrent use — the chassis dispatches ops in parallel on fan-out.

func Open

func Open(name string, cfg Config) (Backend, error)

Open constructs the named backend. Unknown name is an error listing what is available.

func Resolve

func Resolve(providerHint string, cfg Config) (Backend, string, error)

Resolve picks a backend for one ai://decide dispatch, mirroring embed.Resolve:

  • providerHint non-empty → look up by name; NoBackendError if unknown. routing_decision = "provider-override".
  • providerHint empty → first-registered backend. routing_decision = "default".

type CodedError

type CodedError interface {
	error
	Code() string
}

CodedError is implemented by decide errors that carry a stable txco_decide_* code. The ExecAI handler surfaces the code at `<into>.error.code` so a stack can dispatch on it.

type Config

type Config struct {
	// HTTPClient is the chassis-owned http.Client (egress-guarded). Every
	// outbound call from a backend MUST use it.
	HTTPClient *http.Client

	// ZeroDataRetention asks the provider not to retain request data, where
	// the backend can express that (chassis config
	// decide-zero-data-retention).
	ZeroDataRetention bool
}

Config carries decide-package construction options resolved from chassis config. Backends extend it with their own fields without breaking callers (same convention as chat.Config and embed.Config).

type Constructor

type Constructor func(Config) (Backend, error)

Constructor builds a Backend from resolved config.

type InvalidAnswerError

type InvalidAnswerError struct {
	Question string
	Reason   string
}

InvalidAnswerError flags a well-formed provider response whose answers break the contract: a question left unanswered, an answer of the wrong type, a choice that was not offered, a probability out of range. The whole call fails — the op never returns a partial set of answers.

func (*InvalidAnswerError) Code

func (e *InvalidAnswerError) Code() string

func (*InvalidAnswerError) Error

func (e *InvalidAnswerError) Error() string

type InvalidWithError

type InvalidWithError struct {
	Reason string
}

InvalidWithError flags a request the chassis refuses before any provider call: a malformed WITH clause, or a question outside a backend's known limits. Nothing is billed.

func (*InvalidWithError) Code

func (e *InvalidWithError) Code() string

func (*InvalidWithError) Error

func (e *InvalidWithError) Error() string

type MissingSecretError

type MissingSecretError struct {
	Backend string
	Secret  string
}

MissingSecretError is returned when a backend's RequiredSecrets() name is absent from the per-tenant store (and env fallback, when enabled).

func (*MissingSecretError) Code

func (e *MissingSecretError) Code() string

func (*MissingSecretError) Error

func (e *MissingSecretError) Error() string

type NoBackendError

type NoBackendError struct {
	ProviderHint string
	Registered   []string
}

NoBackendError is returned by Resolve when a provider hint names an unregistered backend, or when no backend is registered at all.

func (*NoBackendError) Code

func (e *NoBackendError) Code() string

func (*NoBackendError) Error

func (e *NoBackendError) Error() string

type Option

type Option struct {
	Name        string
	Description string
}

Option is one choice: the name the answer returns, and a description of what it covers.

type ProviderHTTPError

type ProviderHTTPError struct {
	StatusCode int
	Body       string
}

ProviderHTTPError carries a non-2xx provider response (message already extracted, sanitized and bounded by the backend).

func (*ProviderHTTPError) Code

func (e *ProviderHTTPError) Code() string

func (*ProviderHTTPError) Error

func (e *ProviderHTTPError) Error() string

type ProviderNetError

type ProviderNetError struct {
	Reason string
}

ProviderNetError carries a network/DNS failure reaching the provider (after the backend's retry budget is spent).

func (*ProviderNetError) Code

func (e *ProviderNetError) Code() string

func (*ProviderNetError) Error

func (e *ProviderNetError) Error() string

type ProviderParseError

type ProviderParseError struct {
	Reason  string
	BodyLen int
}

ProviderParseError flags an empty or malformed provider response body.

func (*ProviderParseError) Code

func (e *ProviderParseError) Code() string

func (*ProviderParseError) Error

func (e *ProviderParseError) Error() string

type Question

type Question struct {
	// Key is the caller's id for the question; the answer comes back under
	// the same key.
	Key string

	// Type is TypeNoul, TypeChoice, or TypeScore.
	Type string

	// Instructions is the question itself.
	Instructions string

	// Choices are the offered options for a choice question, in author
	// order.
	Choices []Option

	// Levels are a score question's rubric, lowest to highest.
	Levels []string

	// True and False optionally describe what each answer to a noul
	// question means. Empty = not supplied.
	True  string
	False string
}

Question is one bounded question. Which criteria field is set depends on Type.

func ParseQuestions

func ParseQuestions(raw gjson.Result) ([]Question, error)

ParseQuestions decodes and validates the WITH `questions` object:

{ "<key>": { "type": "noul|choice|score", "instructions": "…", "criteria": … } }

criteria by type:

noul    optional; an object whose keys are only "true" and/or "false"
choice  required; an object of option name → description (≥ 1 option)
score   required; an array of level labels, lowest first (≥ 2 levels)

Author order is preserved. Unknown question fields are refused so a typo (`options` for `criteria`) fails loudly instead of being dropped. Provider-specific limits (e.g. a maximum number of choices) are the backend's to enforce before it sends anything.

type Request

type Request struct {
	// State is the evidence every question is asked against: a JSON string,
	// object, or array, passed to the provider as data.
	State json.RawMessage

	// Questions in author order. Keys are unique.
	Questions []Question

	// Model is the resolved model identifier.
	Model string

	// Intent is a trace-only label (e.g. "pony_ingest_place").
	Intent string
}

Request is the chassis-normalized decision request.

type Response

type Response struct {
	// Answers, one per question. After Normalize they are in question order.
	Answers []Answer

	// Provider is the backend name; Model is the model requested.
	Provider string
	Model    string

	// ResolvedModel is the model the provider reports having run, verbatim
	// ("" when it reports none). It may be an alias rather than a version;
	// the chassis records it and invents nothing.
	ResolvedModel string

	// Provider-reported token usage (0 if unreported). Recorded in trace and
	// the envelope; never charged to fuel.
	InputTokens  int64
	OutputTokens int64

	// Cost is the provider-reported cost in USD; nil when unreported.
	Cost *float64

	// GenerationID is the provider's id for this call, for reconciliation.
	GenerationID string

	// LatencyMS is wall-clock from request build to response parse.
	LatencyMS int64

	// Retries is the count of provider retries performed.
	Retries int
}

Response is the chassis-normalized decision result. Backends translate the provider's on-wire shape into this struct.

type TimeoutError

type TimeoutError struct {
	Reason string
}

TimeoutError reports that the op's deadline (WITH timeout, or ai-default-timeout) passed before the provider answered.

func (*TimeoutError) Code

func (e *TimeoutError) Code() string

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

Directories

Path Synopsis
Package vercel is the ai://decide backend for Vercel AI Gateway's evaluation API: POST https://ai-gateway.vercel.sh/v1/evaluate, documented at https://vercel.com/docs/ai-gateway/modalities/evaluation.
Package vercel is the ai://decide backend for Vercel AI Gateway's evaluation API: POST https://ai-gateway.vercel.sh/v1/evaluate, documented at https://vercel.com/docs/ai-gateway/modalities/evaluation.

Jump to

Keyboard shortcuts

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