1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【MLKit x Gemini Nano 4】オンデバイス生成AIでマルチモーダルな推論をしてみる

1
Last updated at Posted at 2026-04-22

はじめに

2026年4月2日、GoogleからGemini Nano 4発表されました。
また、昨年リリースされたML Kit 生成AI APIプロンプトAPIが、モデル選択をサポートするようになり、プレビュー版であるGemini Nano 4も対象になっています。

そこで、早速これらを使用して画像を要約させてみました。

成果物

先に、今回作成したものを見てみましょう。
先日撮影した桜を評価させてみました。どうやらGeminiは桜にも詳しいようです。

output.gif

事前準備

Gemini Nano 4を使用するためには、AICore Developer Preview programへ参加する必要があります。
以下のガイドに従って、参加を済ませてください。

実装

ここでは、ML Kitによるコンテンツ生成の要点だけ抑えていきます。
コードの全文は後半に載せているので、参考にしてみてください。

前提条件

  • Android OS: APIレベル26 (Android 8.0) 以上
  • 対応デバイス: Pixel 9/10, Samsung Galaxy S25/S26 などのAICore搭載端末

ライブラリの導入

アプリレベルの build.gradle.kts に最新のベータ版依存関係を追加します。

build.gradle.kts(:app)
dependencies {
    // ML Kit GenAI Prompt API 
    implementation("com.google.mlkit:genai-prompt:1.0.0-beta2")
}

モデルの選択

最新のモデルを利用するためには、リリースステージとモデルの優先度を明示的に指定します。

モデルの選択
val config = generationConfig {
    modelConfig = modelConfig {
        // Gemini Nano 4 (Developer Preview版) をターゲットにする
        releaseStage = ModelReleaseStage.PREVIEW

        // FULLモデルを明示的に選択(Gemini Nano 4は Fastモデルもある)
        preference = ModelPreference.FULL
    }
}

// クライアントの取得(AICoreとの接続管理はML Kitが内部で実施)
val generativeModel = Generation.getClient(config)

推論の実行(マルチモーダル)

画像とプロンプトを組み合わせたリクエストを作成します。
また、レスポンス形式はストリーミング/非ストリーミングの2つから選択できます。

ストリーミング(逐次出力)
flow {
  val request = generateContentRequest(
    image = ImagePart(bitmap), // 画像
    text = TextPart(prompt)    // テキスト
  ){
      topK = 30
      temperature = 0.4f
      maxOutputTokens = 256
  }
  
  generativeModel.generateContentStream(request).collect { response ->
    val nextText = response.candidates.firstOrNull()?.text
    if (nextText != null) {
      emit(nextText) 
    }
  }
}
非ストリーミング(一括出力)
val request = generateContentRequest(
  image = ImagePart(bitmap), 
  text = TextPart(prompt)
){
    topK = 30
    temperature = 0.4f
    maxOutputTokens = 256
}
// 推論完了までサスペンドし、最終結果のみを取得
val result = generativeModel.generateContent(request).candidates.firstOrNull()?.text

最終的なコード

最終的に、冒頭で示したアプリは以下の実装になっています。
コメントに関してはGeminiに手厚く書いてもらいました。

依存関係
libs.versions.toml
[versions]
agp = "9.2.0-rc01"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
kotlin = "2.2.10"
composeBom = "2026.02.01"
mlkitGenai = "1.0.0-beta2"
markdownRenderer = "0.40.2"
multiplatformMarkdownRendererCoil3 = "0.40.2"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
mlkit-genai-prompt = { group = "com.google.mlkit", name = "genai-prompt", version.ref = "mlkitGenai" }
markdown-renderer-android = { group = "com.mikepenz", name = "multiplatform-markdown-renderer-android", version.ref = "markdownRenderer" }
markdown-renderer-m3 = { group = "com.mikepenz", name = "multiplatform-markdown-renderer-m3", version.ref = "markdownRenderer" }
markdown-renderer-coil3 = { module = "com.mikepenz:multiplatform-markdown-renderer-coil3", version.ref = "multiplatformMarkdownRendererCoil3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

build.gradle.kts(:app)
~~~
dependencies {
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.compose.material3)
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.graphics)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    testImplementation(libs.junit)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.compose.ui.test.junit4)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(libs.androidx.junit)
    debugImplementation(libs.androidx.compose.ui.test.manifest)
    debugImplementation(libs.androidx.compose.ui.tooling)
    implementation(libs.mlkit.genai.prompt)
    implementation(libs.markdown.renderer.m3)
    implementation(libs.markdown.renderer.android)
    implementation(libs.markdown.renderer.coil3)

    implementation(libs.androidx.compose.material.icons.core)
    implementation(libs.androidx.compose.material.icons.extended)
}
コードの全体像
package com.example.gemma4sample

import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.AddPhotoAlternate
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import com.example.gemma4sample.ui.theme.Gemma4SampleTheme
import com.google.mlkit.genai.common.DownloadStatus
import com.google.mlkit.genai.common.FeatureStatus
import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.ImagePart
import com.google.mlkit.genai.prompt.ModelPreference
import com.google.mlkit.genai.prompt.ModelReleaseStage
import com.google.mlkit.genai.prompt.TextPart
import com.google.mlkit.genai.prompt.generateContentRequest
import com.google.mlkit.genai.prompt.generationConfig
import com.google.mlkit.genai.prompt.modelConfig
import com.mikepenz.markdown.coil3.Coil3ImageTransformerImpl
import com.mikepenz.markdown.m3.Markdown
import com.mikepenz.markdown.model.rememberMarkdownState
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import android.util.Log

/**
 * Gemini Nano (Gemma) モデルのダウンロード状態や利用可能性を管理するための Sealed Interface
 */
sealed interface ModelStatus {
    companion object{
        /**
         * ML Kit のステータスコードを ModelStatus 型に変換する
         */
        fun of(status: Int) = when(status){
            FeatureStatus.AVAILABLE -> Available
            FeatureStatus.UNAVAILABLE -> Unavailable
            FeatureStatus.DOWNLOADABLE -> Downloadable
            FeatureStatus.DOWNLOADING -> Downloading(
                0,
                0,
                false
            )
            else -> {
                Log.d("ModelStatus", "Unknown status: $status")
                Unavailable
            }
        }
    }

    /** ML Kit のステータス定数 */
    val status: Int

    /** モデルが利用可能な状態 */
    data object Available: ModelStatus{
        override val status: Int
            get() = FeatureStatus.AVAILABLE

        override fun toString(): String {
            return status.statusText
        }
    }

    /** モデルがダウンロード可能な状態(未ダウンロード) */
    data object Downloadable: ModelStatus{
        override val status: Int
            get() = FeatureStatus.DOWNLOADABLE

        override fun toString(): String {
            return status.statusText
        }
    }

    /** モデルのダウンロードが進行中の状態 */
    data class Downloading(
        val bytesToDownload: Long,
        val totalBytesDownloaded: Long,
        val failed: Boolean
    ): ModelStatus{
        override val status: Int
            get() = FeatureStatus.DOWNLOADING

        override fun toString(): String {
            if (bytesToDownload <= 0) return status.statusText
            val percent = (totalBytesDownloaded.toDouble() / bytesToDownload) * 100
            return String.format("%s (%.1f%%)", status.statusText, percent)
        }
    }

    /** モデルが利用不可能な状態(非対応デバイスなど) */
    data object Unavailable: ModelStatus{
        override val status: Int
            get() = FeatureStatus.UNAVAILABLE

        override fun toString(): String {
            return status.statusText
        }
    }
}

/**
 * Gemini Nano (Gemma) を使用したチャットアプリのメインアクティビティ
 */
class MainActivity : ComponentActivity() {
    /**
     * 生成モデルの設定。プレビューリリースのモデルをフル優先で使用する設定。
     */
    val config = generationConfig {
        modelConfig = modelConfig {
            releaseStage = ModelReleaseStage.PREVIEW
            preference = ModelPreference.FULL
        }
    }

    /**
     * AICore を通じて Gemini Nano のクライアントを取得する
     */
    private val generativeModel = Generation.getClient(config)

    /**
     * デバイス上のモデルの現在の状態。Compose UI から参照される。
     */
    private var modelStatus: ModelStatus by mutableStateOf(ModelStatus.Unavailable)

    /**
     * 現在使用されている、あるいは取得中のモデル名。
     */
    private var modelName by mutableStateOf("Fetching...")

    /**
     * ダウンロード処理の非同期タスクを保持するジョブ。
     */
    private var downloadJob: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // システムバーの背後までコンテンツを描画する設定
        enableEdgeToEdge()
        setContent {
            Gemma4SampleTheme {
                // 基本的な画面レイアウトの骨組み
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Chat(
                        modelName = modelName,
                        modelStatus = modelStatus,
                        modifier = Modifier.padding(innerPadding),
                        doOnQuery = { q, bmp ->
                            // ユーザーの入力に基づいて Gemma に問い合わせを行う
                            queryGemma(q, bmp, 256)
                        },
                        onDownload = {
                            // ダウンロードが必要な場合にモデルの取得を開始する
                            downloadModel()
                        }
                    )
                }
            }
        }
    }

    /**
     * Gemini Nano にクエリ(テキスト、およびオプションで画像)を送信し、
     * 生成される回答を逐次 Flow として返す。
     *
     * @param query ユーザーが入力したプロンプト
     * @param bitmap ユーザーが添付した画像(任意)
     * @param maxTokens 最大出力トークン数
     */
    private fun queryGemma(query: String, bitmap: Bitmap?, maxTokens: Int): Flow<String> = flow {
        try {
            val textPart = TextPart(query)

            // 画像の有無に応じてリクエストを構築
            val request = if (bitmap != null) {
                // 画像が含まれる場合はマルチモーダルリクエスト。
                // 内部処理で元の Bitmap が破棄される可能性があるため、明示的にコピーを渡す。
                val bitmapCopy = bitmap.copy(bitmap.config ?: Bitmap.Config.ARGB_8888, false)

                generateContentRequest(image = ImagePart(bitmapCopy), text = textPart){
                    topK = 30
                    temperature = 0.4f
                    maxOutputTokens = 256
                }
            } else {
                // テキストのみのリクエスト。
                generateContentRequest(text = textPart){
                    topK = 30
                    temperature = 0.4f
                    maxOutputTokens = 256
                }
            }

            // ストリーミング API を使用して、生成されたトークンをリアルタイムに emit する。
            generativeModel.generateContentStream(request).collect { response ->
                val nextText = response.candidates.firstOrNull()?.text
                if (nextText != null) {
                    emit(nextText)
                }
            }
        } catch (e: Exception) {
            emit("Error: ${e.message}")
        }
    }

    /**
     * モデルのダウンロード処理を実行し、その進捗を監視する。
     */
    private fun downloadModel() {
        // 既にダウンロードが進行中の場合は何もしない。
        if (downloadJob?.isActive == true) return
        downloadJob = lifecycleScope.launch {
            try {
                // download() は進捗を通知する Flow を返す。
                generativeModel.download().collect { downloadStatus ->
                    when (downloadStatus) {
                        is DownloadStatus.DownloadStarted -> {
                            modelStatus = ModelStatus.Downloading(
                                totalBytesDownloaded = 0,
                                bytesToDownload = downloadStatus.bytesToDownload,
                                failed = false
                            )
                        }
                        is DownloadStatus.DownloadProgress -> {
                            (modelStatus as? ModelStatus.Downloading)?.let {
                                modelStatus = it.copy(
                                    totalBytesDownloaded = downloadStatus.totalBytesDownloaded
                                )
                            }
                        }
                        is DownloadStatus.DownloadCompleted -> {
                            // 完了したらステータスを再確認して Available に移行させる。
                            checkStatus()
                        }
                        is DownloadStatus.DownloadFailed -> {
                            (modelStatus as? ModelStatus.Downloading)?.let {
                                modelStatus = it.copy(failed = true)
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e("MainActivity", "Download failed or already in progress", e)
                if (modelStatus !is ModelStatus.Downloading) {
                    modelStatus = ModelStatus.Downloading(0, 0, true)
                }
            }
        }
    }

    /**
     * デバイスにおけるモデルの現在の利用可能性と名前を取得して状態を更新する。
     */
    private fun checkStatus() {
        lifecycleScope.launch {
            val statusInt = try {
                generativeModel.checkStatus()
            } catch (e: Exception) {
                FeatureStatus.UNAVAILABLE
            }
            // 利用ステータスの更新
            modelStatus = ModelStatus.of(statusInt)

            // すでにシステム側でダウンロードが始まっている場合は監視を継続する。
            if (statusInt == FeatureStatus.DOWNLOADING) {
                downloadModel()
            }

            try {
                // 使用可能なモデルの識別名を取得。
                modelName = generativeModel.getBaseModelName()
            } catch (e: Exception) {
                modelName = "Unknown"
            }
        }
    }

    override fun onStart() {
        super.onStart()
        // アプリが前面に出た際に最新の状態を確認する。
        checkStatus()
    }
}

/**
 * ML Kit のステータス整数値を人間が読める文字列に変換する拡張プロパティ。
 */
val Int.statusText get() = when (this) {
    FeatureStatus.AVAILABLE -> "Available"
    FeatureStatus.DOWNLOADABLE -> "Downloadable"
    FeatureStatus.DOWNLOADING -> "Downloading"
    FeatureStatus.UNAVAILABLE -> "Unavailable"
    else -> "Unknown"
}

/**
 * チャット画面のメイン UI コンポーザブル。
 */
@Composable
fun Chat(
    modelName: String,
    modelStatus: ModelStatus,
    modifier: Modifier,
    doOnQuery: (q: String, bmp: Bitmap?) -> Flow<String>,
    onDownload: () -> Unit
){
    val context = LocalContext.current
    val coroutineScope = rememberCoroutineScope()
    // AI からのレスポンス全体
    var responseText by remember { mutableStateOf("") }
    // Markdown 表示用の状態管理(スクロール位置などの保持)
    val markdown = rememberMarkdownState(content = responseText, retainState = true)
    // 問い合わせ中かどうかのフラグ
    var isLoading by remember{ mutableStateOf(false) }
    // ユーザーの入力文字列
    var inputText by remember { mutableStateOf("") }
    // 選択された画像データの保持
    var selectedBitmap by remember { mutableStateOf<Bitmap?>(null) }


    // システムのフォトピッカーを呼び出すランチャー。
    val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
        if (uri != null) {
            // URI から Bitmap へデコード。
            val source = ImageDecoder.createSource(context.contentResolver, uri)
            selectedBitmap = ImageDecoder.decodeBitmap(source)
        }
    }


    Column(
        modifier = modifier
            .fillMaxSize()
            .imePadding() // ソフトウェアキーボード表示時に内容が隠れないように調整。
            .verticalScroll(rememberScrollState())
            .padding(16.dp)
    ) {
        // デバッグ・情報用:モデル名とステータス。
        Text("model: $modelName")
        Text("status: ${modelStatus}")

        // チャット履歴/回答表示。
        LazyColumn(
            modifier = modifier.weight(1f)
        ) {
            item {
                // Markdown 形式での描画。
                Markdown(
                    markdownState = markdown,
                    modifier = Modifier.fillMaxWidth(),
                    imageTransformer = Coil3ImageTransformerImpl
                )
            }
        }

        // 入力エリア。
        TextInputArea(
            modelAvailable = modelStatus is ModelStatus.Available,
            isLoading = isLoading,
            inputText = inputText,
            selectedBitmap = selectedBitmap,
            onInputTextChanged = { inputText = it },
            onPickImageClick = {
                // フォトピッカー起動。
                pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
            },
            onClearBitmap = {
                selectedBitmap = null
            },
            onQueryClick = {
                coroutineScope.launch {
                    responseText = ""
                    isLoading = true
                    // doOnQuery からの Flow を集端し、回答を構築する。
                    doOnQuery(inputText, selectedBitmap).collect {
                        responseText += it
                    }
                    isLoading = false
                }
            }
        )

        // モデルが未準備(ダウンロード可能、または不可)な場合に表示するセットアップ用ボタン。
        if (modelStatus is ModelStatus.Downloadable || modelStatus is ModelStatus.Unavailable) {
            Button(onClick = onDownload, modifier = Modifier.fillMaxWidth()) {
                Text("Download Model / Initialize")
            }
        }
    }
}

/**
 * テキスト入力、画像添付、送信ボタンを含むコンポーネント。
 */
@Composable
fun TextInputArea(
    modelAvailable: Boolean,
    isLoading: Boolean,
    inputText: String,
    selectedBitmap: Bitmap?,
    onInputTextChanged: (String)-> Unit,
    onPickImageClick: ()-> Unit,
    onQueryClick: ()-> Unit,
    onClearBitmap: ()-> Unit,
){
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .clip(RoundedCornerShape(10.dp))
            .background(Color.Gray)
            .padding(5.dp)
    ) {
        // メインのテキスト入力フィールド。
        OutlinedTextField(
            modifier = Modifier
                .background(Color.Transparent)
                .fillMaxWidth(),
            colors = OutlinedTextFieldDefaults.colors(
                focusedBorderColor = Color.Transparent,
                unfocusedBorderColor = Color.Transparent,
                disabledBorderColor = Color.Transparent,
                errorBorderColor = Color.Transparent
            ),
            value = inputText,
            onValueChange = onInputTextChanged,
            enabled = !isLoading
        )

        // 下部のコントロールボタン類。
        Row(
            Modifier
                .fillMaxWidth()
                .padding(horizontal = 5.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.Bottom
        ) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                // 画像添付ボタン。
                Icon(
                    modifier = Modifier
                        .size(32.dp)
                        .clickable(
                            enabled = true,
                            onClick = onPickImageClick
                        ),
                    imageVector = Icons.Default.AddPhotoAlternate,
                    contentDescription = "Attach Image",
                )

                Spacer(modifier = Modifier.width(8.dp))
                // 添付済み画像がある場合のプレビューと削除。
                if (selectedBitmap != null) {
                    Image(
                        bitmap = selectedBitmap!!.asImageBitmap(),
                        contentDescription = "Selected image preview",
                        modifier = Modifier.size(32.dp)
                    )
                    Spacer(modifier = Modifier.width(8.dp))
                    Button(onClick = onClearBitmap) {
                        Text("Clear")
                    }
                }
            }

            // 処理中ならインジケータ、待機中なら送信ボタン。
            if(isLoading){
                CircularProgressIndicator(
                    modifier = Modifier.size(32.dp)
                )
            }else{
                Icon(
                    modifier = Modifier
                        .size(32.dp)
                        .clickable(
                            enabled = modelAvailable && inputText.isNotBlank(),
                            onClick = onQueryClick
                        ),
                    imageVector = Icons.AutoMirrored.Filled.Send,
                    contentDescription = "Send Message",
                )
            }
        }
    }
}

/**
 * 入力エリアのプレビュー。
 */
@Preview
@Composable
fun PreviewTextInputArea() = TextInputArea(
    modelAvailable = false,
    isLoading = false,
    inputText = "Sample Query",
    selectedBitmap = null,
    onInputTextChanged = { },
    onPickImageClick = { },
    onQueryClick = { },
    onClearBitmap = { },
)

/**
 * チャット画面全体のプレビュー(ダウンロード中状態)。
 */
@Preview
@Composable
fun PreviewChat() = Chat(
    modelName = "Gemma-2b",
    modelStatus = ModelStatus.Downloading(
        100, 10, false
    ),
    modifier = Modifier,
    doOnQuery = { _, _ -> flow {}  },
    onDownload = { },
)

まとめ

ML Kitを使用することで、とても簡単にGemini Nanoを使用することができました。

インターネットを必要としないので、クローズドな環境であったり、重要な情報を扱いたい場面でも安心して使用することができます。
一方で、端末内で動作するということは、それなりの端末性能が要求されます。

今はまだハイエンド端末が中心ですが、これから対応機種が増えていくことで、オンデバイスAIがより身近なものになっていくのが楽しみです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?