Decision Maker

The MediaPipe Decision Maker task evaluates structured decision questions—such as categorical choices, boolean conditions, and rubric scores—as a deterministic, calibrated, zero-decoding decision engine, powered by a variety of lightweight models capable of running on-device, including EmbeddingGemma 1 and 2, bidirectional cross-encoders (Laya, GLiNER2.5-Decide), and generative Gemma 4.

Instead of generating free-form text or fragile JSON tokens through computationally expensive autoregressive decoding, MediaPipe Decision Maker task evaluates structured decision questions in a single forward pass. MediaPipe Decision Maker task returns calibrated probabilities, winning labels, and continuous expected scores with minimal runtime overhead. For detailed guidance on choosing between bi-encoders, cross-encoders, and generative decoders—plus option phrasing, prior normalization, and prewarm() patterns—see Prompting and schema design best practices.

Quickstart

Install the SDK for your platform, initialize the task with a model, and run inference.

Web

npmnpm install @mediapipe/tasks-decisionWeb demo·Edit in StackBlitz·Setup guide
import { type ChoiceQuestion, DecisionMaker, FilesetResolver } from "@mediapipe/tasks-decision";

// 1. Create the task
const MODEL_URL = "https://huggingface.co/litert-community/embeddinggemma-2-text-270m-litert-lm/resolve/main/embeddinggemma-2-text-270m.litertlm";

const decision = await FilesetResolver.forDecisionTasks(
  "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-decision@latest/wasm"
);
const decisionMaker = await DecisionMaker.createFromOptions(decision, {
  baseOptions: { modelAssetPath: MODEL_URL, delegate: "GPU" },
  maxNumTokens: 512,
});

// 2. Run inference
const categoryQuestion: ChoiceQuestion = {
  criteria: {
    billing: "Refunds, invoices, or subscription charges.",
    technical: "Crashes, login errors, or broken features.",
  },
  normalizePrior: true,
};
const inputContext = "App crashes immediately after logging in.";
const result = await decisionMaker.evaluateChoice(inputContext, categoryQuestion);

Android (Kotlin)

Gradleimplementation("com.google.mediapipe:tasks-decision:latest.release")Sample code·Setup guide
import com.google.mediapipe.tasks.decision.BooleanQuestion
import com.google.mediapipe.tasks.decision.ChoiceQuestion
import com.google.mediapipe.tasks.decision.DecisionMaker
import com.google.mediapipe.tasks.decision.DecisionMakerOptions

// 1. Create the task
val options = DecisionMakerOptions.builder()
    .setModelPath("embeddinggemma-2-text-270m.litertlm")
    .setMaxNumTokens(512)
    .build()
val decisionMaker = DecisionMaker.createFromOptions(context, options)

// 2. Run inference
val intentQuestion = ChoiceQuestion.withDescriptions(
    options = mapOf(
        "billing" to "Refunds, invoices, or subscription charges.",
        "technical" to "Crashes, login errors, or broken features.",
    ),
    normalizePrior = true,
)
decisionMaker.prewarm(intentQuestion)

val inputContext = "App crashes immediately after logging in."
val choiceResult = decisionMaker.evaluate(inputContext, intentQuestion)

val urgentQuestion = BooleanQuestion(condition = "The user is blocked from using the application.")
val booleanResult = decisionMaker.evaluate(inputContext, urgentQuestion)

Android (Java)

Gradleimplementation 'com.google.mediapipe:tasks-decision:latest.release'Sample code·Setup guide
import com.google.mediapipe.tasks.decision.BooleanQuestion;
import com.google.mediapipe.tasks.decision.BooleanResult;
import com.google.mediapipe.tasks.decision.ChoiceQuestion;
import com.google.mediapipe.tasks.decision.ChoiceResult;
import com.google.mediapipe.tasks.decision.DecisionMaker;
import com.google.mediapipe.tasks.decision.DecisionMakerDelegate;
import com.google.mediapipe.tasks.decision.DecisionMakerOptions;
import com.google.mediapipe.tasks.decision.ScoringMode;
import java.util.Map;

// 1. Create the task
DecisionMakerOptions options = new DecisionMakerOptions(
    "embeddinggemma-2-text-270m.litertlm",
    null,
    null,
    512,
    DecisionMakerDelegate.CPU
);
DecisionMaker decisionMaker = DecisionMaker.createFromOptions(context, options);

// 2. Run inference
ChoiceQuestion intentQuestion = ChoiceQuestion.withDescriptions(
    Map.of(
        "billing", "Refunds, invoices, or subscription charges.",
        "technical", "Crashes, login errors, or broken features."
    ),
    0.0f,
    "",
    ScoringMode.LENGTH_NORMALIZED_LOG_PROB,
    true
);
decisionMaker.prewarm(intentQuestion);

String inputContext = "App crashes immediately after logging in.";
ChoiceResult choiceResult = decisionMaker.evaluate(inputContext, intentQuestion);

BooleanQuestion urgentQuestion = new BooleanQuestion(
    "The user is blocked from using the application.",
    0.5f,
    0.0f,
    false
);
BooleanResult booleanResult = decisionMaker.evaluate(inputContext, urgentQuestion);

iOS

SwiftPMhttps://github.com/google-ai-edge/mediapipe → MediaPipeTasksDecisionPackage.swift·Sample code·Setup guide
import Foundation
import MediaPipeTasksDecision

// 1. Create the task
guard let modelPath = Bundle.main.path(
    forResource: "embeddinggemma-2-text-270m",
    ofType: "litertlm"
) else { return }

let options = DecisionMakerOptions(modelPath: modelPath, maxNumTokens: 512, delegate: .cpu)
let decisionMaker = try DecisionMaker(options: options)

// 2. Run inference
let intentQuestion = ChoiceQuestion(
    options: [
        (key: "billing", description: "Refunds, invoices, or subscription charges."),
        (key: "technical", description: "Crashes, login errors, or broken features.")
    ],
    normalizePrior: true
)
try decisionMaker.prewarm(question: intentQuestion)

let inputText = "App crashes immediately after logging in."
let choiceResult = try decisionMaker.evaluate(text: inputText, question: intentQuestion)

Python

pippip install mediapipeSample code·Setup guide
import mediapipe as mp

BaseOptions = mp.tasks.BaseOptions
ChoiceQuestion = mp.tasks.decision.ChoiceQuestion
DecisionMaker = mp.tasks.decision.DecisionMaker
DecisionMakerOptions = mp.tasks.decision.DecisionMakerOptions

# 1. Create the task
options = DecisionMakerOptions(
    base_options=BaseOptions(model_asset_path="embeddinggemma-2-text-270m.litertlm"),
    max_num_tokens=512,
)

with DecisionMaker.create_from_options(options) as maker:
    # 2. Run inference
    intent_q = ChoiceQuestion(
        criteria={
            "billing": "Refunds, invoices, or subscription charges.",
            "technical": "Crashes, login errors, or broken features.",
        },
        normalize_prior=True,
    )
    maker.prewarm_choice(intent_q)

    context_text = "App crashes immediately after logging in."
    res = maker.evaluate_choice(context_text, intent_q)
    batch_res = maker.evaluate_batch(
        contexts=["Double charge error", "App crashes on start"],
        questions={"intent": intent_q},
    )

Task details

This section describes the capabilities, inputs, outputs, and configuration options of this task.

Features

  • Zero-decoding decision inference: Eliminates token generation latency by scoring structured decision primitives directly from model representations.
  • Multimodal support: Accepts UTF-8 text across all backends, plus RGB images (MPImage, JPEG/PNG bytes) with EmbeddingGemma 2 (440M and 740M) and 16 kHz audio samples with EmbeddingGemma 2 Text / Vision / Audio (740M) in both the input context and decision options.
  • First-class JSON integration: Built-in JSON parsing utilities evaluate TypeSafe Jev JSON payloads and OpenAI json_schema specifications without custom adapter code.
  • Cross-platform consistency: Uniform API architecture available across Android (Kotlin), iOS (Swift), Python, and Web (TypeScript/JavaScript).

Input and output formats

  • Input Context: Multimodal state containing text, image (440M and 740M EmbeddingGemma 2), or 16 kHz audio clips (740M EmbeddingGemma 2).
  • Question Primitives:
    • ChoiceQuestion: Multi-class categorical decision among K options.
    • BooleanQuestion: Binary condition validation (True / False or Yes / No).
    • ScoreQuestion: Ordinal rubric grading with automated expected score calculation.
  • Outputs: Winning key, option probabilities, boolean value, and expected continuous score.

Supported model backends

Model Backend Architecture Typical Latency Key Strengths
EmbeddingGemma 1 & 2 Bi-Encoder (270M / 300M / 440M / 740M) Ultra-low latency High throughput, multimodal text/vision/audio scoring (440M & 740M), multi-span LogSumExp document pooling, 0 MB extra RAM using UniversalEmbedder.
Gemma 4 E2B & E4B Autoregressive Decoder (2B / 4B) Single Prefill Pass Complex compositional logic, counterfactual policy exceptions.
Laya & GLiNER2.5-Decide Cross-Encoder (149M / 166M) Single Cross-Attention Pass Joint context-label attention.

Execution lifecycle

The Decision Maker operates on a three-step execution lifecycle designed to optimize latency and memory:

  1. Create (DecisionMaker): Initialize the decision engine from a model path, file descriptor, or buffer. Existing UniversalEmbedder providers can also be shared to achieve 0 MB extra RAM consumption.
  2. Prewarm (prewarm / Prewarm / prewarmJson): Executed once during setup. For embedding backends, it computes candidate option embeddings, performs within-question centroid whitening, and L2-normalizes vector representations. For logit backends, it pre-populates KV cache prefixes.
  3. Evaluate (evaluate / Evaluate / evaluateJson): Executed per request. Scores context queries against cached decision structures in a single pass, returning structured results with ultra-low latency per question on CPU.

Configuration options

This task has the following configuration options:

Web

Option Name Description Value Range Default Value
baseOptions.modelAssetPath Source of the .litertlm, .task, or .tflite model asset (baseOptions.modelAssetPath / modelAssetBuffer on Web, modelPath / modelAssetFileDescriptor / modelAssetBuffer on Android, modelSource / modelPath / modelData on iOS, or base_options.model_asset_path / model_asset_buffer in Python). Path / Buffer Required
maxNumTokens Maximum number of tokens allowed for input context processing. Integer 4096
baseOptions.delegate Hardware acceleration delegate (CPU or GPU). On Web, .litertlm containers with external weights (EmbeddingGemma 2, Gemma 4) require 'GPU' (WebGPU); 'CPU' on Web is supported for .task or inline .tflite models (such as Laya). {CPU, GPU} GPU

Android

Option Name Description Value Range Default Value
modelPath Source of the .litertlm, .task, or .tflite model asset (modelPath, modelAssetFileDescriptor, or modelAssetBuffer on DecisionMakerOptions). Path / FD / Buffer Required
maxNumTokens Maximum number of tokens allowed for input context processing. Integer 4096
delegate Hardware acceleration delegate (DecisionMakerDelegate.CPU or GPU). {CPU, GPU} CPU

iOS

Option Name Description Value Range Default Value
modelPath Source of the .litertlm, .task, or .tflite model asset (modelSource, modelPath, or modelData on DecisionMakerOptions). Path / Data Required
maxNumTokens Maximum number of tokens allowed for input context processing. Integer 4096
delegate Hardware acceleration delegate (.cpu or .gpu). {CPU, GPU} CPU

Python

Option Name Description Value Range Default Value
base_options.model_asset_path Source of the .litertlm, .task, or .tflite model asset (base_options.model_asset_path or model_asset_buffer). Path / Buffer Required
max_num_tokens Maximum number of tokens allowed for input context processing. Integer 4096
base_options.delegate Hardware acceleration delegate (BaseOptions.Delegate.CPU or GPU). {CPU, GPU} CPU

Task output

Depending on the evaluation method called (evaluate on Android/iOS, evaluateBoolean / evaluateChoice / evaluateScore on Web, or evaluate_boolean / evaluate_choice / evaluate_score in Python), DecisionMaker returns a structured BooleanResult, ChoiceResult, or ScoreResult:

Field Result Type Type / Range Description
value BooleanResult boolean true when probabilityTrue meets or exceeds the question threshold (default 0.5).
probabilityTrue BooleanResult float ([0.0, 1.0]) Calibrated probability that the condition holds (probability_true in Python).
selectedKey ChoiceResult, ScoreResult string Key of the winning option or highest-probability rubric level (selected_key in Python).
probabilities ChoiceResult, ScoreResult Record<string, float> ([0.0, 1.0]) Normalized probability distribution across all K options or rubric levels (levelProbabilities on Android/iOS for ScoreResult).
predictionSet ChoiceResult string[] 90% conformal prediction set of candidate option keys (prediction_set in Python).
expectedScore ScoreResult float ([0.0, K - 1]) Continuous probability-weighted expected score across the ordered rubric levels (expected_score in Python).
confidence All results float ([0.0, 1.0]) Normalized certainty score derived from probability margin and entropy.

The following examples show the return values for BooleanQuestion, ChoiceQuestion, and ScoreQuestion:

// BooleanResult (from evaluate / evaluateBoolean)
{ value: true, probabilityTrue: 0.9420, confidence: 0.8840 }

// ChoiceResult (from evaluate / evaluateChoice)
{
  selectedKey: "lights_control",
  probabilities: { lights_control: 0.9120, thermostat: 0.0510, security_check: 0.0370 },
  predictionSet: ["lights_control"],
  confidence: 0.8610
}

// ScoreResult (from evaluate / evaluateScore)
{
  selectedKey: "Routine preference adjustment",
  expectedScore: 0.18,
  probabilities: { "Routine preference adjustment": 0.8200, "Moderate": 0.1500, "Critical": 0.0300 },
  confidence: 0.7900
}

Models

Decision Maker pipeline overview
Model name Input Quantization type Model Card Versions
EmbeddingGemma 2 Text (270M) Text context and options (string, Dict[string, string], or List[string]) int4 Model card Latest
EmbeddingGemma 2 Text / Vision (440M) Text and image context and options (string, Dict[string, string], List[string], MPImage, or JPEG/PNG bytes) Mixed Precision (int4+int8) Model card Latest
EmbeddingGemma 2 Text / Vision / Audio (740M) Text, image, and 16 kHz audio context and options (string, Dict[string, string], List[string], MPImage / JPEG/PNG bytes, or 16 kHz audio samples) Mixed Precision (int2/int4/int8) Model card Latest
EmbeddingGemma 300M Text context (string, Dict[string, string], or List[string]) Mixed Precision (int4+int8) Model card Latest
Gemma 4 E2B Text context (string, Dict[string, string], or List[string]) int4 Model card Latest
Gemma 4 E4B Text context (string, Dict[string, string], or List[string]) int4 Model card Latest
Laya (256-token) Text context (string, Dict[string, string], or List[string]) float32 Model card Latest
Laya (512-token) Text context (string, Dict[string, string], or List[string]) float32 Model card Latest
GLiNER2.5-Decide (256-token) Text context (string, Dict[string, string], or List[string]) float16 Model card Latest
GLiNER2.5-Decide (512-token) Text context (string, Dict[string, string], or List[string]) float16 Model card Latest

Decision primitives

All decision tasks in the MediaPipe Decision Maker task are composed of three fundamental decision question primitives (see Task output for result field schemas):

Primitive Question Type Result Type Description
Boolean BooleanQuestion BooleanResult Evaluates whether a natural-language condition holds against the context using a configurable threshold (default 0.5).
Choice ChoiceQuestion ChoiceResult Selects 1 of K mutually exclusive candidate options defined by text descriptions, images, or audio samples.
Score ScoreQuestion ScoreResult Evaluates an ordered K-level rubric from lowest (0) to highest (K - 1) grade and computes the continuous probability-weighted expected score.

Migration and comparison

This section details how MediaPipe Decision Maker task compares to cloud alternatives, such as OpenAI Structured Outputs and server-side Jev APIs.

Architectural comparison

Dimension MediaPipe Decision Maker TypeSafe Jev API OpenAI Structured Outputs API
Execution Target On-Device Edge (Android, iOS, WebGPU/Wasm, Desktop, Python) Server / Cloud / Python Harness (Vertex AI, Gemini API) Cloud HTTP API (/v1/chat/completions, json_schema)
Underlying Engine Multi-Backend Engine:
1. Bi-Encoder (EmbeddingGemma 1 & 2)
2. Cross-Encoder (Laya, GLiNER2.5)
3. Single-pass Logits (Gemma 4)
Prompted Generative Logits (Jev-Gemma) or Bi-Encoder Dot-Product (Jev-Embed) Autoregressive Constrained Decoding (FSM token masking over JSON Schema)
Core Primitives ChoiceQuestion, BooleanQuestion, ScoreQuestion choice, noul (boolean), score Arbitrary JSON Schema (enum, boolean, number), no built-in expected value calculation
Option Caching (Prewarm) Explicit Prewarm() API: Embeds and whitens all K options once up front; runtime executes one query forward pass (O(1) in K). Re-evaluates prompt or option embeddings per request unless manually cached. Server-side automatic prefix caching only (still executes token decoding).
Multimodal Support Built-in for Context AND Options (EmbeddingGemma 2 440M/740M): Accepts text, MPImage/JPEG/PNG, and 16 kHz audio in inputs and candidates. Text / JSON state only (state: dict | str). Multimodal in input prompt, text-only in output schema options.
Latency and Memory Ultra-low latency per question on CPU, 157 MB bundle (0 MB extra RAM sharing UniversalEmbedder). Network RTT or uncached Python GPU inference. Network RTT, billed per input and output token.

Built-in JSON support

Both TypeSafe Jev JSON payloads and OpenAI json_schema definitions are parsed directly by DecisionMaker (evaluateJson and prewarmJson) with zero custom adapter code required.

Evaluate unmodified Jev payloads ("state" + "questions"):

{
  "state": "Cancel my flight and refund my credit card immediately.",
  "questions": {
    "intent": {
      "type": "choice",
      "instructions": "Determine the user's primary travel intent.",
      "normalize_prior": true,
      "criteria": {
        "book_flight": "Search or book a new flight itinerary.",
        "cancel_refund": "Cancel an existing reservation and request a refund.",
        "baggage_claim": "Report lost or delayed luggage."
      }
    },
    "is_financial": {
      "type": "noul",
      "condition": "The request involves a payment, charge, or refund.",
      "threshold": 0.5
    }
  }
}

DecisionMaker automatically maps standard OpenAI json_schema property objects onto the built-in Decision Maker decision primitives:

OpenAI JSON Schema property Decision Maker primitive mapping Decision Maker behavior
{"type": "string", "enum": [...], "description": "..."} ChoiceQuestion(choices=enum, instructions=description) Evaluates calibrated probabilities for every enum value in 1 non-generative pass.
{"type": "boolean", "description": "..."} BooleanQuestion(condition=description) Calibrated probability of True with customizable decision threshold.
{"type": "integer", "minimum": 1, "maximum": 5, "description": "..."} ScoreQuestion(rubric=["1".."5"], instructions=description) Calculates continuous expected score rather than quantized integer.

Best practices

This section outlines best practices for designing schemas, writing prompts, and configuring parameters in MediaPipe Decision Maker task across its three model architectures:

  1. Bi-Encoders (EmbeddingGemma 1 & 2)
  2. Autoregressive Generative Models (Gemma 4 E2B & E4B)
  3. Bidirectional Cross-Encoders (Laya, GLiNER2.5-Decide)

Because scoring mechanics vary across these model families, following these guidelines ensures maximum accuracy, lower latency, and calibrated predictions.

Do's and don'ts

Design Dimension Do (Recommended) Don't (Avoid) Why It Matters
Option Descriptions (Choice & Score) Provide rich, self-contained semantic descriptions for every key (e.g., "billing_refund": "Disputed charges, refund requests, or invoice errors"). Avoid passing bare opaque keys ("0", "1", "cat_a") with empty descriptions or repeated boilerplate. Bi-encoders project options into metric space; distinct semantic phrases maximize separation between options.
Boolean Conditions (Boolean) Phrase conditions as single, affirmative, testable propositions (e.g., "The customer is requesting a refund or billing credit"). Avoid using double negations ("The request is NOT ineligible") or compound "A unless B" exceptions inside a single string. Affirmative propositions align cleanly with internal hypothesis templates across all model backends.
Prior Normalization (normalize_prior) Enable normalize_prior = True when option descriptions vary significantly in length, specificity, or frequency. Avoid leaving normalize_prior = False when one catch-all option is much broader or more generic than others. Subtracts the null-context baseline score (0 ms cost when prewarmed) so generic labels don't dominate borderline inputs.
Temperature Calibration (temperature) Leave temperature = 0.0 (default) to use auto-calibrated temperatures (approximately 0.045 for EmbeddingGemma, 1.0 for Gemma 4 / Laya). Avoid hardcoding temperature = 1.0 on EmbeddingGemma unless unscaled cosine probabilities are specifically required. Raw cosine similarities live in a narrow -0.15 to +0.35 band. Default T = 1.0 yields near-uniform probabilities (ECE > 0.30), while auto-calibrated T = 0.045 achieves ECE = 0.048.
3-Way Verification & Abstention Use standard keys ("supported", "contradicted", "insufficient") with distinct evidence descriptions. Avoid mixing multiple overlapping "unsure" / "maybe" options in the same question without clear definitions. The compiler recognizes 3-way verification schemas, handles neutral centroid subtraction, and prevents decision bias.
Static vs. Dynamic Schemas Call prewarm() once at startup for static or repeatedly evaluated schemas. Avoid re-compiling or omitting prewarm() on hot loops with large option sets (10 or more options). prewarm() caches option embeddings (EmbeddingGemma) and KV prefixes (Gemma 4), reducing runtime evaluation to O(1) in the number of options.

Schema design rules

Follow these design rules to get the most out of any MediaPipe Decision Maker task model.

For ChoiceQuestion and ScoreQuestion, always pair concise keys with clear, descriptive strings.

import mediapipe as mp

BooleanQuestion = mp.tasks.decision.BooleanQuestion
ChoiceQuestion = mp.tasks.decision.ChoiceQuestion

# GOOD: Rich, distinct semantic descriptions
support_routing = ChoiceQuestion(
    criteria={
        "billing_refund": "Disputed charges, refund requests, invoice errors, or payment failures.",
        "technical_bug": "App crash, UI freeze, login failure, sync error, or broken functionality.",
        "feature_request": "Suggestions for new capabilities, UI enhancements, or integrations.",
    }
)

# AVOID: Bare keys with empty or repetitive descriptions
bad_routing = ChoiceQuestion(
    criteria={
        "option_1": "Option 1",
        "option_2": "Option 2",
    }
)

Avoid double negatives or tricky condition phrasing. Keep boolean conditions affirmative and concise.

# GOOD: Direct, affirmative condition
is_vip = BooleanQuestion(condition="The customer holds an active Premium or Enterprise tier subscription.")

# AVOID: Double negatives
is_not_unsubscribed = BooleanQuestion(condition="The user does NOT want to opt out of notifications.")

When one option is naturally broader than others (e.g., an "other_general" catch-all category), enable prior normalization. This evaluates the schema without context at prewarm time and subtracts the baseline preference at zero runtime cost.

normalized_choice = ChoiceQuestion(
    criteria={
        "password_reset": "Specific requests to reset account password or unlock 2FA.",
        "general_inquiry": "General questions about company policies, hours, or contact info.",
    },
    normalize_prior=True,
)

Model optimization

Follow the guide for your chosen model family.

  • Models: EmbeddingGemma 2 (270M Text, 440M Text / Vision, 740M Text / Vision / Audio), EmbeddingGemma 1 (300M)
  • Performance: Ultra-low latency on CPU and GPU
  • Architecture: Symmetric bi-encoder embedding context and candidate options into a shared 768D L2-normalized vector space.
  1. Provide Concrete Exemplars: Include specific synonyms and keywords in option descriptions (e.g., "technical_bug": "App crash, freeze, login failure, sync error, or broken UI").
  2. Focus Instructions on the Decision Axis: Keep optional instructions concise (1 sentence) describing what dimension to classify (e.g., "Classify the primary intent of the support ticket.").
  3. Automatic Long-Context Handling: For long documents (> 256 tokens, up to 2,048 tokens), DecisionMaker automatically chunks context into overlapping 512-token spans using head-anchored LogSumExp pooling. Placing key metadata near the top of the context takes advantage of the head-span anchor weight (0.35).
  4. Use Rubric Descriptions with ScoreQuestion: For ordinal ratings (1–5), populate descriptions for each level. Decision Maker computes the probability-weighted expected score across levels, yielding smooth continuous scores with higher correlation than discrete argmax picking.
  • Models: Gemma 4 E2B (2B) & E4B (4B)
  • Performance: Evaluates decisions in a single prefill forward pass with zero autoregressive token generation.
  1. Leverage for Complex Policy Rules: Ideal for tasks involving counterfactual reasoning, multi-rule exceptions, or complex conditional logic where bi-encoders struggle.
  2. Put Explicit Rules in Instructions: Place explicit constraints directly in the instructions field (e.g., "Approve refund ONLY IF purchased within 14 days; EXCEPT digital downloads which are non-refundable unless unplayed.").
  3. Prewarm for Prefix Caching: Always call prewarm() so DecisionMaker caches the KV states of the prompt prefix.
  • Models: Laya (149M), GLiNER2.5-Decide (166M)
  • Architecture: Bidirectional DeBERTa/ModernBERT cross-encoders that jointly attend to the input context and candidate options within a 256- or 512-token window.
  1. Keep Option Sets Compact When Possible: When all candidate options and the input context fit within the model's 256- or 512-token window (typically 8 or fewer options), DecisionMaker packs all options alongside the context into a single encoder forward pass (~4× faster than pairwise evaluation). For larger option sets or very long option descriptions, DecisionMaker automatically falls back to evaluating options across multiple passes.
  2. Keep Option Descriptions Concise: Use high-signal 5–15 word descriptions to save token budget for the context.

Common pitfalls

Avoid these common problems and apply the recommended fixes.

Problem: Bi-encoders compute independent embeddings for context and options. When a single question contains base rules, "unless" exceptions, and counterfactual overrides, lexical overlap can pull the bi-encoder toward the wrong label.

  • Solution A — Decompose into atomic BooleanQuestions (Recommended): Because Decision Maker embeds the input context only once and reuses the query vector across all prewarmed questions, evaluating 3 atomic boolean questions adds minimal additional latency over evaluating a single question.

    # AVOID: Single compound question with "unless" / "except" clauses on Bi-Encoders
    bad_policy = BooleanQuestion(
        condition=(
            "The order is eligible for a refund (purchased within 30 days, UNLESS it is "
            "a final-sale clearance item, EXCEPT when the item arrived physically damaged)."
        )
    )
    
    # RECOMMENDED on Bi-Encoders (EG1 / EG2): Decompose into atomic BooleanQuestions.
    # Decision Maker embeds the input context ONCE and evaluates all 3 conditions.
    # `maker` is a DecisionMaker instance created as shown in the Quickstart.
    atomic_policy_schema = {
        "within_30_days": BooleanQuestion(condition="The item was purchased within the last 30 days."),
        "is_clearance": BooleanQuestion(condition="The item is a final-sale or clearance item."),
        "arrived_damaged": BooleanQuestion(
            condition="The item arrived physically damaged, broken, or defective."
        ),
    }
    
    order_text = "Purchased 10 days ago on final-sale clearance, arrived cracked."
    res = maker.evaluate_batch(contexts=[order_text], questions=atomic_policy_schema)[0]
    
    # Combine booleans deterministically in application logic
    is_eligible = res["arrived_damaged"].value or (
        res["within_30_days"].value and not res["is_clearance"].value
    )
    
  • Solution B — Route to Gemma 4 or Cross-Encoder models: If your application accepts arbitrary, user-defined policy prompts that cannot be decomposed ahead of time, route evaluations to Gemma 4 or Laya, which perform full cross-attention across rule exceptions and context.

Problem: Negated conditions ("is NOT ineligible") or keys sharing root words ("not_spam" versus "spam") create semantic ambiguity.

  • Avoid: python BooleanQuestion(condition="The user does NOT want to opt out of notifications.") ChoiceQuestion(criteria={"not_spam": "Legitimate email", "spam": "Unsolicited email"})

  • Do instead: python BooleanQuestion(condition="The user wants to receive notifications.") ChoiceQuestion( criteria={ "legitimate_inbox": "Personal, work, or transactional message expected by user.", "unsolicited_spam": "Promotional bulk mail, phishing, or unwanted advertisement.", } )

Problem: Opaque numeric keys like "0", "1", "2" with empty descriptions provide no semantic vector target for bi-encoders.

  • Avoid: python ChoiceQuestion(criteria={"0": "", "1": "", "2": ""})
  • Do instead: python ChoiceQuestion( criteria={ "0": "No defect visible upon inspection.", "1": "Minor cosmetic scratch or superficial blemish.", "2": "Severe structural crack or functional failure.", } )