Skip to content
// EFFECT DAYS 2026

DecisionModel unstable

Defines the provider-neutral service for structured decisions. decide encodes one input as JSON, sends its named decisions in one provider call, and validates the answers. Failures are reported as AiError values. decideSettled makes the same call but reports each decision separately, so valid answers survive when others are missing, refused, or invalid.

22 exports Since v4.0.0 Source

Constructors

make

unstable
Since v4.0.0

Creates a DecisionModel that encodes inputs as JSON and validates provider answers. Answers must cover every decision and use its labels. Distributions must sum to 1 within 1e-6, optional confidence must be in [0, 1], and ratings must be in [0, criteria.length - 1], and boxes must have edges in [0, 1] that do not cross. With decide, refused decisions fail with AiError.ContentPolicyError and other unanswered decisions fail with AiError.InvalidOutputError; decideSettled reports them per decision instead. Encoding failures use AiError.InvalidUserInputError.

Details

After encoding, each Decision.Image in the input is replaced with a reference string, and the provider receives the images in images. Set supportsImages: true to accept images. Otherwise, an input with images fails with AiError.InvalidUserInputError.

Set supportsLocate: true to accept locate decisions; only then does decide receive them. Otherwise, a definition with a locate decision fails with AiError.InvalidUserInputError, as does a locate decision whose path holds no image.

Providers that round each probability to probabilityPrecision decimal places may return distributions whose sum drifts from 1 by up to half a unit of the last place per label. Setting probabilityPrecision accepts that drift and rescales the distribution to sum to 1; larger drift still fails.

See

Signature

declare function make(params: {
readonly decide: (options: ProviderOptions<Probability | Classify<string> | Rate<string>>) => Effect<ProviderResponse, AiError>;
readonly probabilityPrecision?: number;
readonly supportsImages?: boolean;
readonly supportsLocate?: false;
} | {
readonly decide: (options: ProviderOptions) => Effect<ProviderResponse, AiError>;
readonly probabilityPrecision?: number;
readonly supportsImages?: boolean;
readonly supportsLocate: true;
}): Effect<DecisionModel>

Decisions

decide

unstable
Since v4.0.0

Answers a decision definition using the current DecisionModel service. Encodes the input with Schema.toCodecJson, requiring the schema's encoding services. Explicit undefined fields become null; absent fields stay absent. Custom declarations need a JSON codec annotation or encoding fails. Returned answers and probability dictionaries have null prototypes. Fails with AiError.ContentPolicyError if the provider refuses any decision, and otherwise with AiError.InvalidOutputError for the first unanswered decision in definition order.

See

Signature

declare function decide<Input extends Constraint, Decisions extends Record<string, Any>>(definition: Definition<Input, Decisions>, options: DecideOptions<Input>): Effect<DecideResponse<Decisions>, AiError, DecisionModel | Input["EncodingServices"]>

Example Triaging a ticket

import { Effect, Schema } from "effect"
import { Decision, DecisionModel } from "effect/ai"
const TicketTriage = Decision.make({
input: Schema.String,
decisions: {
urgent: Decision.probability({
instructions: "The message is time-sensitive",
criteria: { false: "No time pressure", true: "Needs action now" }
})
}
})
const program = Effect.gen(function*() {
const { answers, usage } = yield* DecisionModel.decide(TicketTriage, {
input: "My card was charged twice, please fix this today"
})
return { probability: answers.urgent.probability, usage }
})
Since v4.0.6

Answers each decision with a Result containing a validated answer or a Decision.Unanswered reason. Input encoding and provider failures still fail the effect. Token usage covers the whole call.

Details

Provider response decoding errors fail the whole call. Per-decision reasons reflect the first failed validation check; "NoDistribution" requires an otherwise valid answer.

See

  • decide for failing unless every decision is answered

Signature

declare function decideSettled<Input extends Constraint, Decisions extends Record<string, Any>>(definition: Definition<Input, Decisions>, options: DecideOptions<Input>): Effect<DecideSettledResponse<Decisions>, AiError, DecisionModel | Input["EncodingServices"]>

Example Falling back for unanswered decisions

import { Effect, Result, Schema } from "effect"
import { Decision, DecisionModel } from "effect/ai"
const TicketTriage = Decision.make({
input: Schema.String,
decisions: {
department: Decision.classify({
instructions: "Which team should handle this",
criteria: { billing: "payments", technical: "bugs" }
}),
urgent: Decision.probability({ instructions: "The message is time-sensitive" })
}
})
const program = Effect.gen(function*() {
const { outcomes } = yield* DecisionModel.decideSettled(TicketTriage, {
input: "My card was charged twice, please fix this today"
})
const department = Result.isSuccess(outcomes.department) &&
outcomes.department.success.probabilities.billing >= 0.8
? "billing"
: "needs review"
const urgent = Result.isSuccess(outcomes.urgent) ? outcomes.urgent.success.probability : undefined
return { department, urgent }
})

Models

DecideResponse

interface unstable
Since v4.0.0

Answers for every decision in a definition with usage and response metadata.

See

Signature

interface DecideResponse<Decisions extends Record<string, Decision.Any>> {
readonly answers: Decision.Answers<Decisions>;
readonly id?: string;
readonly metadata: DecideResponseMetadata;
readonly modelId?: string;
readonly usage: DecisionUsage;
}

DecideResponseMetadata

interface unstable
Since v4.0.6

Provider-specific response metadata keyed by provider name. Provider packages augment this interface.

Signature

interface DecideResponseMetadata extends ProviderMetadata {
[key: string]: Json;
}

DecideSettledResponse

interface unstable
Since v4.0.6

Per-decision outcomes, token usage, and response metadata for the whole request.

See

Signature

interface DecideSettledResponse<Decisions extends Record<string, Decision.Any>> {
readonly id?: string;
readonly metadata: DecideResponseMetadata;
readonly modelId?: string;
readonly outcomes: Decision.Outcomes<Decisions>;
readonly usage: DecisionUsage;
}

DecisionModel

interface unstable
Since v4.0.0

Decision operations over a definition.

Signature

interface DecisionModel {
readonly "~effect/ai/DecisionModel": "~effect/ai/DecisionModel";
readonly decide: <Input extends Constraint, Decisions extends Record<string, Any>>(definition: Definition<Input, Decisions>, options: DecideOptions<Input>) => Effect<DecideResponse<Decisions>, AiError, Input["EncodingServices"]>;
readonly decideSettled: <Input extends Constraint, Decisions extends Record<string, Any>>(definition: Definition<Input, Decisions>, options: DecideOptions<Input>) => Effect<DecideSettledResponse<Decisions>, AiError, Input["EncodingServices"]>;
}
Since v4.0.0

Provider-reported token usage. Unreported counts are undefined.

Signature

declare class DecisionUsage extends {
readonly inputTokens?: number;
readonly outputTokens?: number;
} {
constructor(...args: [props?: {
readonly inputTokens?: number;
readonly outputTokens?: number;
}, options?: MakeOptions]);
}

Image

interface unstable
Since v4.0.1

An image in the input, declared with a Decision.Image field. data is a base64 string, a byte array, or a URL.

Signature

interface Image {
readonly data: string | Uint8Array<ArrayBufferLike> | URL;
readonly mediaType: string;
}

ProviderAnswer

type unstable
Since v4.0.0

Provider answer for any decision kind.

Signature

type ProviderAnswer = ProviderClassifyAnswer | ProviderRateAnswer | ProviderProbabilityAnswer | ProviderRefusalAnswer | ProviderLocateAnswer

ProviderClassifyAnswer

interface unstable
Since v4.0.0

Provider classify answer. Omit probabilities when no distribution is available.

Signature

interface ProviderClassifyAnswer {
readonly _tag: "Classify";
readonly confidence?: number;
readonly label: string;
readonly probabilities?: Readonly<Record<string, number>>;
}

ProviderImage

interface unstable
Since v4.1.0

An image extracted from the encoded input. state holds reference where the image was, and path is where it was first found. data is a base64 string or a URL.

Signature

interface ProviderImage {
readonly image: {
readonly data: string | URL;
readonly mediaType: string;
};
readonly path: readonly Array<string | number>;
readonly reference: string;
}

ProviderLocateAnswer

interface unstable
Since v4.1.0

Provider answer for a locate decision.

Signature

interface ProviderLocateAnswer {
readonly _tag: "Locate";
readonly box: BoundingBox;
readonly confidence?: number;
}

ProviderProbabilityAnswer

interface unstable
Since v4.0.0

Provider answer for a probability decision.

Signature

interface ProviderProbabilityAnswer {
readonly _tag: "Probability";
readonly probability: number;
}

ProviderRateAnswer

interface unstable
Since v4.0.0

Provider rate answer. The highest probability determines the label, with ties going to the first criterion. Omit probabilities when unavailable.

Signature

interface ProviderRateAnswer {
readonly _tag: "Rate";
readonly confidence?: number;
readonly probabilities?: Readonly<Record<string, number>>;
readonly rating: number;
}

ProviderRefusalAnswer

interface unstable
Since v4.0.6

Provider refusal to answer a decision.

Signature

interface ProviderRefusalAnswer {
readonly _tag: "Refusal";
}

ProviderResponse

interface unstable
Since v4.0.0

Provider response for a decision request. answers is keyed like the requested decisions. Each answer is validated against its decision before it is returned to the caller. Omit the key of a decision the provider did not answer. id, modelId and metadata are passed through; metadata defaults to {}.

Signature

interface ProviderResponse {
readonly answers: Readonly<Record<string, ProviderAnswer>>;
readonly id?: string;
readonly metadata?: DecideResponseMetadata;
readonly modelId?: string;
readonly usage: {
readonly inputTokens: number | undefined;
readonly outputTokens: number | undefined;
};
}

Options

DecideOptions

interface unstable
Since v4.0.0

Options for answering a decision definition.

Signature

interface DecideOptions<Input extends Schema.Constraint> {
readonly input: Input["Type"];
}

ProviderOptions

interface unstable
Since v4.0.0

Provider input options for a decision request. state is encoded with Schema.toCodecJson, not stringified. All decisions must be answered in one call. images holds the Decision.Image values found in the input and is omitted when there are none. Decisions is the union of decision kinds the provider receives.

Signature

interface ProviderOptions<Decisions extends Decision.Any = Decision.Any> {
readonly decisions: Record<string, Decisions>;
readonly images?: readonly Array<ProviderImage>;
readonly state: Json;
}

Services

Since v4.0.0

Service key for answering decisions about an input. Create implementations with make.

See

  • decide for answering a definition through the current service

Signature

declare const DecisionModel: Context.Service<DecisionModel, DecisionModel>

Type IDs

TypeId

unstable
Since v4.0.0

Brand for DecisionModel implementations.

Signature

declare const TypeId: "~effect/ai/DecisionModel"

TypeId

type unstable
Since v4.0.0

Brand type for DecisionModel.

Signature

type TypeId = "~effect/ai/DecisionModel"