Decision maker guide for Android

The MediaPipe Decision Maker task Android API provides a Kotlin-first SDK (com.google.mediapipe.tasks.decision) for executing on-device decision questions using foundation models like EmbeddingGemma on Android devices.

For more information about the capabilities, models, and configuration options of this task, see the Overview.

Code example

The Android Kotlin SDK (com.google.mediapipe.tasks.decision) provides the DecisionMakerOptions.decisionMakerOptions { ... } DSL, BooleanQuestion, ChoiceQuestion (with ChoiceQuestion.withDescriptions(...)), ScoreQuestion, multimodal DecisionContext, batch evaluation, and built-in JSON schema evaluation (prewarmJson / evaluateJson):

package com.example.ondevicedecision

import android.content.Context
import com.google.mediapipe.tasks.core.BaseOptions
import com.google.mediapipe.tasks.decision.DecisionMaker
import com.google.mediapipe.tasks.decision.DecisionMakerOptions
import com.google.mediapipe.tasks.decision.DecisionMakerDelegate
import com.google.mediapipe.tasks.decision.BooleanQuestion
import com.google.mediapipe.tasks.decision.ChoiceQuestion
import com.google.mediapipe.tasks.decision.ScoreQuestion
import com.google.mediapipe.tasks.decision.DecisionContext

class OnDeviceRouter(context: Context) : AutoCloseable {

  // 1. Configure DecisionMakerOptions with Kotlin DSL (supports asset path, FileDescriptor, or DirectByteBuffer)
  private val options = decisionMakerOptions {
    modelPath = "embeddinggemma-2-270m-it.litertlm"
    maxNumTokens = 512
    delegate = DecisionMakerDelegate.CPU
  }

  private val decisionMaker = DecisionMaker.createFromOptions(context, options)

  // 2. Define typed questions & prewarm option embeddings once
  private val toolQuestion = ChoiceQuestion.withDescriptions(
    options = mapOf(
      "set_alarm" to "Set a wake-up alarm or countdown timer.",
      "send_message" to "Send an SMS or chat message to a contact.",
      "capture_note" to "Save a memo, shopping item, or voice note.",
      "none" to "No tool needed; answer conversationally.",
    ),
    instructions = "Select the on-device tool to invoke for the user request.",
    normalizePrior = true,
  )

  private val urgentQuestion = BooleanQuestion(
    condition = "The request specifies an immediate deadline or urgent reminder.",
    threshold = 0.5f,
    normalizePrior = true,
  )

  private val clarityQuestion = ScoreQuestion(
    rubric = listOf(
      "Vague or missing required parameters",
      "Partially specified parameters",
      "Fully specified with clear parameters",
    ),
    instructions = "How explicitly does the user state the required tool parameters?",
  )

  init {
    decisionMaker.prewarm(toolQuestion)
  }

  // 3a. Evaluate typed questions directly
  fun routeUtterance(userText: String) {
    val action = decisionMaker.evaluate(userText, toolQuestion)
    val urgent = decisionMaker.evaluate(userText, urgentQuestion)
    val clarity = decisionMaker.evaluate(userText, clarityQuestion)

    println("Tool: ${action.selectedKey} (p=${action.probabilities[action.selectedKey]}, conf=${action.confidence})")
    println("Urgent: ${urgent.value} (pTrue=${urgent.probabilityTrue})")
    println("Clarity [0..1]: ${clarity.expectedScore} (level=${clarity.selectedKey})")
  }

  // 3b. Multimodal DecisionContext + Multi-Question JSON Evaluation in a single call
  fun routeMultimodal(userText: String, jpegBytes: ByteArray) {
    val ctx = DecisionContext(text = userText, imageBytes = jpegBytes)
    val jsonResponse = decisionMaker.evaluateJson(
      context = ctx,
      jsonQuestions = """
        {
          "questions": {
            "screen_issue": {
              "type": "choice",
              "instructions": "Classify the hardware issue visible in the photo.",
              "criteria": {
                "cracked_screen": "Physical glass crack or shattered OLED panel.",
                "water_damage": "Liquid corrosion or condensation."
              }
            }
          }
        }
      """.trimIndent(),
    )
    println("Multimodal JSON Result: $jsonResponse")
  }

  override fun close() = decisionMaker.close()
}

Setup

Place your .litertlm or .tflite model file (e.g., embeddinggemma-2-270m-it.litertlm) inside your project's app/src/main/assets/ directory.

Dependencies

Decision Maker uses the com.google.mediapipe:tasks-decision library. Add this dependency to the build.gradle file of your Android app development project. Import the required dependencies with the following code:

dependencies {
    ...
    implementation 'com.google.mediapipe:tasks-decision:latest.release'
}

Model

The MediaPipe Decision Maker task requires a trained model that is compatible with this task. For more information on available trained models for Decision Maker, see the task overview Models section.

Download the recommended model asset and place it in your Android app's assets/ directory:

  • EmbeddingGemma 2 (270M): embeddinggemma-2-270m-it.litertlm

Select and download the model, and then store it within your project directory:

<dev-project-root>/src/main/assets

Specify the path of the model within the ModelName parameter.

Create the task

Configure DecisionMakerOptions using BaseOptions and instantiate DecisionMaker:

val baseOptions = BaseOptions.builder()
    .setModelAssetPath("embeddinggemma-2-270m-it.litertlm")
    .build()

val options = DecisionMakerOptions.builder()
    .setBaseOptions(baseOptions)
    .setMaxNumTokens(512)
    .build()

val decisionMaker = DecisionMaker.createFromOptions(context, options)

Prewarm options

Prewarm static candidate questions during setup to eliminate runtime encoding overhead:

val intentQuestion = ChoiceQuestion.builder()
    .setInstructions("Route customer inquiry to the appropriate queue.")
    .setCriteria(mapOf(
        "billing_refund" to "Disputed charges, refund requests, or invoice errors.",
        "technical_bug" to "App crashes, login failures, or sync issues.",
        "account_cancel" to "Account closure or subscription cancellation."
    ))
    .setNormalizePrior(true)
    .build()

// Prewarm candidates in memory
decisionMaker.prewarmChoice(intentQuestion)

Prepare data

Construct a DecisionContext containing text, image, or multimodal inputs:

val inputContext = DecisionContext.builder()
    .setText("App crashes immediately after logging in on Android 14.")
    .build()

Run the task

Execute evaluation synchronously using evaluateChoice, evaluateBoolean, or evaluateScore:

val choiceResult = decisionMaker.evaluateChoice(inputContext, intentQuestion)

val urgentQuestion = BooleanQuestion.builder()
    .setCondition("The user is blocked from using the application.")
    .build()

val booleanResult = decisionMaker.evaluateBoolean(inputContext, urgentQuestion)

Handle and display results

The following shows an example of the output data from this task:

// BooleanResult
value: true
probabilityTrue: 0.942
confidence: 0.884

// ChoiceResult
selectedKey: "lights_control"
probabilities: ["lights_control": 0.912, "thermostat": 0.051, "security_check": 0.037]
confidence: 0.861

// ScoreResult
selectedKey: "Routine preference adjustment"
expectedScore: 0.18
levelProbabilities: [0.82, 0.15, 0.03]
confidence: 0.79

Inspect predicted categories, probabilities, and 90% conformal prediction sets:

Log.d("DecisionMaker", "Selected Key: ${choiceResult.selectedKey}")
Log.d("DecisionMaker", "Confidence: ${choiceResult.confidence}")
Log.d("DecisionMaker", "Conformal Set: ${choiceResult.predictionSet}")

Log.d("DecisionMaker", "Is Urgent: ${booleanResult.value}")
Log.d("DecisionMaker", "P(True): ${booleanResult.probabilityTrue}")

Use

The Android SDK uses a Kotlin DSL for configuration, strongly typed question primitives, and support for both direct object evaluation and multi-question JSON schemas.

1. Initialize options and MediaPipe Decision Maker task

Use decisionMakerOptions to configure model path, token limits, and delegate acceleration (CPU or GPU):

import android.content.Context
import com.google.mediapipe.tasks.decision.BooleanQuestion
import com.google.mediapipe.tasks.decision.ChoiceQuestion
import com.google.mediapipe.tasks.decision.DecisionContext
import com.google.mediapipe.tasks.decision.DecisionMaker
import com.google.mediapipe.tasks.decision.DecisionMakerDelegate
import com.google.mediapipe.tasks.decision.DecisionMakerOptions.Companion.decisionMakerOptions
import com.google.mediapipe.tasks.decision.ScoreQuestion

class OnDeviceRouter(context: Context) : AutoCloseable {

    private val options = decisionMakerOptions {
        modelPath = "embeddinggemma-2-270m-it.litertlm"
        maxNumTokens = 512
        delegate = DecisionMakerDelegate.CPU
    }

    private val decisionMaker = DecisionMaker.createFromOptions(context, options)

2. Define questions and prewarm

Construct ChoiceQuestion, BooleanQuestion, and ScoreQuestion instances. Call prewarm once during initialization to precompute and whiten option embeddings at 0 ms runtime evaluation cost:

    private val toolQuestion = ChoiceQuestion.withDescriptions(
        options = mapOf(
            "set_alarm" to "Set a wake-up alarm or countdown timer.",
            "send_message" to "Send an SMS or chat message to a contact.",
            "capture_note" to "Save a memo, shopping item, or voice note.",
            "none" to "No tool needed; answer conversationally."
        ),
        instructions = "Select the on-device tool to invoke for the user request.",
        normalizePrior = true
    )

    private val urgentQuestion = BooleanQuestion(
        condition = "The request specifies an immediate deadline or urgent reminder.",
        threshold = 0.5f,
        normalizePrior = true
    )

    private val clarityQuestion = ScoreQuestion(
        rubric = listOf(
            "Vague or missing required parameters",
            "Partially specified parameters",
            "Fully specified with clear parameters"
        ),
        instructions = "How explicitly does the user state the required tool parameters?"
    )

    init {
        // Embeds and whitens candidate options once up front
        decisionMaker.prewarm(toolQuestion)
    }

3. Evaluate single and typed questions

Pass input text and questions to evaluate:

    fun routeUtterance(userText: String) {
        val action = decisionMaker.evaluate(userText, toolQuestion)
        val urgent = decisionMaker.evaluate(userText, urgentQuestion)
        val clarity = decisionMaker.evaluate(userText, clarityQuestion)

        println("Selected Tool: ${action.selectedKey}")
        println("Tool Probability: ${action.probabilities[action.selectedKey]}")
        println("Confidence: ${action.confidence}")

        println("Is Urgent: ${urgent.value} (pTrue=${urgent.probabilityTrue})")
        println("Clarity Score [0..1]: ${clarity.expectedScore} (Level Key=${clarity.selectedKey})")
    }

Multimodal DecisionContext and JSON schemas

The Android SDK supports multimodal context inputs (combining text with JPEG/PNG image bytes or audio) and evaluating complex JSON question schemas using evaluateJson:

    fun routeMultimodal(userText: String, jpegBytes: ByteArray) {
        val ctx = DecisionContext(text = userText, imageBytes = jpegBytes)

        val jsonResponse = decisionMaker.evaluateJson(
            context = ctx,
            jsonQuestions = """
            {
              "questions": {
                "screen_issue": {
                  "type": "choice",
                  "instructions": "Classify the hardware issue visible in the photo.",
                  "criteria": {
                    "cracked_screen": "Physical glass crack or shattered OLED panel.",
                    "water_damage": "Liquid corrosion or condensation."
                  }
                }
              }
            }
            """.trimIndent()
        )

        println("Multimodal JSON Evaluation Result: $jsonResponse")
    }

    override fun close() {
        decisionMaker.close()
    }
}

API summary reference

Method Description
DecisionMaker.createFromOptions(context, options) Creates a new instance using specified options.
decisionMaker.prewarm(question) Embeds and whitens candidates for a given question.
decisionMaker.evaluate(text, question) Evaluates a single typed question against text context.
decisionMaker.evaluateBatch(contexts, questions) Evaluates multiple contexts across questions in batch.
decisionMaker.evaluateJson(context, jsonQuestions) Automatically parses and evaluates Jev or OpenAI JSON schemas against text/multimodal context.
decisionMaker.close() Releases underlying JNI and model resources.