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
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)
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)
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
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
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_schemaspecifications 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:
- Create (
DecisionMaker): Initialize the decision engine from a model path, file descriptor, or buffer. ExistingUniversalEmbedderproviders can also be shared to achieve 0 MB extra RAM consumption. - 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. - 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. |
Example DecisionMaker result payloads
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
| 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.
1. Built-in Jev JSON payload format (evaluateJson)
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
}
}
}
2. Built-in OpenAI json_schema property mapping
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:
- Bi-Encoders (EmbeddingGemma 1 & 2)
- Autoregressive Generative Models (Gemma 4 E2B & E4B)
- 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.
1. Write self-contained option descriptions
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",
}
)
2. Phrase boolean conditions affirmatively
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.")
3. Use prior normalization for unequal options
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.
EmbeddingGemma 1 & 2 (Bi-Encoders)
- 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.
- 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"). - 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."). - Automatic Long-Context Handling: For long documents (> 256 tokens, up to
2,048 tokens),
DecisionMakerautomatically 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). - 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.
Gemma 4 (E2B & E4B)
- Models: Gemma 4 E2B (2B) & E4B (4B)
- Performance: Evaluates decisions in a single prefill forward pass with zero autoregressive token generation.
- Leverage for Complex Policy Rules: Ideal for tasks involving counterfactual reasoning, multi-rule exceptions, or complex conditional logic where bi-encoders struggle.
- Put Explicit Rules in Instructions: Place explicit constraints directly
in the
instructionsfield (e.g.,"Approve refund ONLY IF purchased within 14 days; EXCEPT digital downloads which are non-refundable unless unplayed."). - Prewarm for Prefix Caching: Always call
prewarm()soDecisionMakercaches the KV states of the prompt prefix.
Laya & GLiNER2.5-Decide (Cross-Encoders)
- 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.
- 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),
DecisionMakerpacks 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,DecisionMakerautomatically falls back to evaluating options across multiple passes. - 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.
Pitfall 1: Unsplit counterfactual or "unless" exceptions on bi-encoders
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.
Pitfall 2: Double negations and negated option keys
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.", } )
Pitfall 3: Bare numeric or code keys without descriptions
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.", } )