Mulai Menggunakan LiteRT-LM di Android

Kotlin API LiteRT-LM untuk Android dan JVM (Linux, MacOS, Windows) dengan fitur seperti akselerasi GPU dan NPU, multi-modalitas, dan penggunaan alat.

Pengantar

Berikut adalah contoh aplikasi chat terminal yang dibuat dengan Kotlin API:

import com.google.ai.edge.litertlm.*

suspend fun main() {
  Engine.setNativeMinLogSeverity(LogSeverity.ERROR) // Hide log for TUI app

  val engineConfig = EngineConfig(modelPath = "/path/to/model.litertlm")
  Engine(engineConfig).use { engine ->
    engine.initialize()

    engine.createConversation().use { conversation ->
      while (true) {
        print("\n>>> ")
        conversation.sendMessageAsync(readln()).collect { print(it) }
      }
    }
  }
}

Demo untuk kode contoh Kotlin

Untuk mencoba contoh di atas, clone repo dan jalankan dengan example/Main.kt:

bazel run -c opt //kotlin/java/com/google/ai/edge/litertlm/example:main -- <abs_model_path>

Model .litertlm yang tersedia ada di HuggingFace LiteRT Community. Animasi di atas menggunakan Gemma3-1B-IT.

Untuk contoh Android, lihat aplikasi Google AI Edge Gallery.

Mulai Menggunakan Gradle

Meskipun LiteRT-LM dikembangkan dengan Bazel, kami menyediakan paket Maven untuk pengguna Gradle/Maven.

1. Menambahkan dependensi Gradle

dependencies {
    // For Android
    implementation("com.google.ai.edge.litertlm:litertlm-android:latest.release")

    // For JVM (Linux, MacOS, Windows)
    implementation("com.google.ai.edge.litertlm:litertlm-jvm:latest.release")
}

Anda dapat menemukan versi yang tersedia di Google Maven di litertlm-android dan litertlm-jvm.

latest.release dapat digunakan untuk mendapatkan rilis terbaru.

2. Menginisialisasi Engine

Engine adalah titik entri ke API. Lakukan inisialisasi dengan jalur model dan konfigurasi. Jangan lupa untuk menutup mesin untuk melepaskan resource.

Catatan: Metode engine.initialize() dapat memerlukan waktu yang cukup lama (misalnya, hingga 10 detik) untuk memuat model. Sebaiknya panggil ini di thread atau coroutine latar belakang untuk menghindari pemblokiran thread UI.

import com.google.ai.edge.litertlm.Backend
import com.google.ai.edge.litertlm.Engine
import com.google.ai.edge.litertlm.EngineConfig

val engineConfig = EngineConfig(
    modelPath = "/path/to/your/model.litertlm", // Replace with your model path
    backend = Backend.GPU(), // Or Backend.NPU(nativeLibraryDir = "...")
    // Optional: Pick a writable dir. This can improve 2nd load time.
    // cacheDir = "/tmp/" or context.cacheDir.path (for Android)
)

val engine = Engine(engineConfig)
engine.initialize()
// ... Use the engine to create a conversation ...

// Close the engine when done
engine.close()

Di Android, untuk menggunakan backend GPU, aplikasi perlu meminta library native yang bergantung secara eksplisit dengan menambahkan berikut ini ke AndroidManifest.xml di dalam tag <application>:

  <application>
    <uses-native-library android:name="libvndksupport.so" android:required="false"/>
    <uses-native-library android:name="libOpenCL.so" android:required="false"/>
  </application>

Untuk menggunakan backend NPU, Anda mungkin perlu menentukan direktori yang berisi library NPU. Di Android, jika library digabungkan dengan aplikasi Anda, tetapkan ke context.applicationInfo.nativeLibraryDir. Lihat LiteRT-LM NPU untuk mengetahui detail selengkapnya tentang library native NPU.

val engineConfig = EngineConfig(
    modelPath = modelPath,
    backend = Backend.NPU(nativeLibraryDir = context.applicationInfo.nativeLibraryDir)
)

3. Membuat Percakapan

Setelah mesin diinisialisasi, buat instance Conversation. Anda dapat memberikan ConversationConfig untuk menyesuaikan perilakunya.

import com.google.ai.edge.litertlm.ConversationConfig
import com.google.ai.edge.litertlm.Message
import com.google.ai.edge.litertlm.SamplerConfig

// Optional: Configure the system instruction, initial messages, sampling
// parameters, etc.
val conversationConfig = ConversationConfig(
    systemInstruction = Contents.of("You are a helpful assistant."),
    initialMessages = listOf(
        Message.user("What is the capital city of the United States?"),
        Message.model("Washington, D.C."),
    ),
    samplerConfig = SamplerConfig(topK = 10, topP = 0.95, temperature = 0.8),
)

val conversation = engine.createConversation(conversationConfig)
// Or with default config:
// val conversation = engine.createConversation()

// ... Use the conversation ...

// Close the conversation when done
conversation.close()

Conversation mengimplementasikan AutoCloseable, sehingga Anda dapat menggunakan blok use untuk pengelolaan resource otomatis untuk percakapan sekali pakai atau singkat:

engine.createConversation(conversationConfig).use { conversation ->
    // Interact with the conversation
}

4. Mengirim Pesan

Ada tiga cara untuk mengirim pesan:

  • sendMessage(contents): Message: Panggilan sinkron yang diblokir hingga model menampilkan respons lengkap. Hal ini lebih sederhana untuk interaksi permintaan/respons dasar.
  • sendMessageAsync(contents, callback): Panggilan asinkron untuk streaming respons. Metode ini lebih baik untuk permintaan yang berjalan lama atau saat Anda ingin menampilkan respons saat sedang dibuat.
  • sendMessageAsync(contents): Flow<Message>: Panggilan asinkron yang menampilkan Kotlin Flow untuk respons streaming. Ini adalah pendekatan yang direkomendasikan bagi pengguna Coroutine.

Contoh Sinkron:

import com.google.ai.edge.litertlm.Content
import com.google.ai.edge.litertlm.Message

print(conversation.sendMessage("What is the capital of France?"))

Contoh Asinkron dengan callback:

Gunakan sendMessageAsync untuk mengirim pesan ke model dan menerima respons melalui callback.

import com.google.ai.edge.litertlm.Content
import com.google.ai.edge.litertlm.Message
import com.google.ai.edge.litertlm.MessageCallback
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

val callback = object : MessageCallback {
    override fun onMessage(message: Message) {
        print(message)
    }

    override fun onDone() {
        // Streaming completed
    }

    override fun onError(throwable: Throwable) {
        // Error during streaming
    }
}

conversation.sendMessageAsync("What is the capital of France?", callback)

Contoh Asinkron dengan Alur:

Gunakan sendMessageAsync (tanpa argumen callback) untuk mengirim pesan ke model dan menerima respons melalui Flow Kotlin.

import com.google.ai.edge.litertlm.Content
import com.google.ai.edge.litertlm.Message
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch

// Within a coroutine scope
conversation.sendMessageAsync("What is the capital of France?")
    .catch { ... } // Error during streaming
    .collect { print(it.toString()) }

5. Multi-Modalitas

Objek Message dapat berisi berbagai jenis Content, termasuk Text, ImageBytes, ImageFile, dan AudioBytes, AudioFile.

// Initialize the `visionBackend` and/or the `audioBackend`
val engineConfig = EngineConfig(
    modelPath = "/path/to/your/model.litertlm", // Replace with your model path
    backend = Backend.CPU(), // Or Backend.GPU() or Backend.NPU(...)
    visionBackend = Backend.GPU(), // Or Backend.NPU(...)
    audioBackend = Backend.CPU(), // Or Backend.NPU(...)
)

// Sends a message with multi-modality.
// See the Content class for other variants.
conversation.sendMessage(Contents.of(
    Content.ImageFile("/path/to/image"),
    Content.AudioBytes(audioBytes), // ByteArray of the audio
    Content.Text("Describe this image and audio."),
))

6. Menentukan dan Menggunakan Alat

Ada dua cara untuk menentukan alat:

  1. Dengan fungsi Kotlin (direkomendasikan untuk sebagian besar kasus)
  2. Dengan spesifikasi Open API (kontrol penuh atas spesifikasi dan eksekusi alat)

Menentukan Alat dengan Fungsi Kotlin

Anda dapat menentukan fungsi Kotlin kustom sebagai alat yang dapat dipanggil model untuk melakukan tindakan atau mengambil informasi.

Buat class yang mengimplementasikan ToolSet dan anotasikan metode dengan @Tool dan parameter dengan @ToolParam.

import com.google.ai.edge.litertlm.Tool
import com.google.ai.edge.litertlm.ToolParam

class SampleToolSet: ToolSet {
    @Tool(description = "Get the current weather for a city")
    fun getCurrentWeather(
        @ToolParam(description = "The city name, e.g., San Francisco") city: String,
        @ToolParam(description = "Optional country code, e.g., US") country: String? = null,
        @ToolParam(description = "Temperature unit (celsius or fahrenheit). Default: celsius") unit: String = "celsius"
    ): Map<String, Any> {
        // In a real application, you would call a weather API here
        return mapOf("temperature" to 25, "unit" to  unit, "condition" to "Sunny")
    }

    @Tool(description = "Get the sum of a list of numbers.")
    fun sum(
        @ToolParam(description = "The numbers, could be floating point.") numbers: List<Double>,
    ): Double {
        return numbers.sum()
    }
}

Di balik layar, API memeriksa anotasi ini dan tanda tangan fungsi untuk menghasilkan skema gaya OpenAPI. Skema ini menjelaskan fungsi alat, parameter (termasuk jenis dan deskripsinya dari @ToolParam), dan jenis nilai yang ditampilkan ke model bahasa.

Jenis Parameter

Jenis untuk parameter yang dianotasi dengan @ToolParam dapat berupa String, Int, Boolean, Float, Double, atau List dari jenis ini (misalnya, List<String>). Gunakan jenis nullable (misalnya, String?) untuk menunjukkan parameter yang dapat bernilai null. Tetapkan nilai default untuk menunjukkan bahwa parameter bersifat opsional, dan sebutkan nilai default dalam deskripsi di @ToolParam.

Jenis Nilai yang Ditampilkan

Jenis nilai yang ditampilkan fungsi alat Anda dapat berupa jenis Kotlin apa pun. Hasil akan dikonversi menjadi elemen JSON sebelum dikirim kembali ke model.

  • Jenis List dikonversi menjadi array JSON.
  • Jenis Map dikonversi menjadi objek JSON.
  • Jenis primitif (String, Number, Boolean) dikonversi ke primitif JSON yang sesuai.
  • Jenis lainnya dikonversi menjadi string dengan metode toString().

Untuk data terstruktur, sebaiknya tampilkan Map atau class data yang akan dikonversi menjadi objek JSON.

Menentukan Alat dengan Spesifikasi OpenAPI

Atau, Anda dapat menentukan alat dengan menerapkan class OpenApiTool dan memberikan deskripsi alat sebagai string JSON yang sesuai dengan spesifikasi Open API. Metode ini berguna jika Anda sudah memiliki skema OpenAPI untuk alat Anda atau jika Anda memerlukan kontrol terperinci atas definisi alat.

import com.google.ai.edge.litertlm.OpenApiTool

class SampleOpenApiTool : OpenApiTool {

    override fun getToolDescriptionJsonString(): String {
        return """
        {
          "name": "addition",
          "description": "Add all numbers.",
          "parameters": {
            "type": "object",
            "properties": {
              "numbers": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "description": "The list of numbers to sum."
            },
            "required": [
              "numbers"
            ]
          }
        }
        """.trimIndent() // Tip: trim to save tokens
    }

    override fun execute(paramsJsonString: String): String {
        // Parse paramsJsonString with your choice of parser/deserializer and
        // execute the tool.

        // Return the result as a JSON string
        return """{"result": 1.4142}"""
    }
}

Alat Pendaftaran

Sertakan contoh alat Anda dalam ConversationConfig.

val conversation = engine.createConversation(
    ConversationConfig(
        tools = listOf(
            tool(SampleToolSet()),
            tool(SampleOpenApiTool()),
        ),
        // ... other configs
    )
)

// Send messages that might trigger the tool
conversation.sendMessageAsync("What's the weather like in London?", callback)

Model akan memutuskan kapan harus memanggil alat berdasarkan percakapan. Hasil dari eksekusi alat akan otomatis dikirim kembali ke model untuk membuat respons akhir.

Panggilan Alat Manual

Secara default, panggilan alat yang dihasilkan oleh model akan otomatis dieksekusi oleh LiteRT-LM dan hasil dari eksekusi alat akan otomatis dikirim kembali ke model untuk menghasilkan respons berikutnya.

Jika Anda ingin menjalankan alat secara manual dan mengirimkan hasilnya kembali ke model, Anda dapat menyetel automaticToolCalling di ConversationConfig ke false.

val conversation = engine.createConversation(
    ConversationConfig(
        tools = listOf(
            tool(SampleOpenApiTool()),
        ),
        automaticToolCalling = false,
    )
)

Jika menonaktifkan panggilan alat otomatis, Anda harus menjalankan alat secara manual dan mengirimkan hasilnya kembali ke model dalam kode aplikasi. Metode execute dari OpenApiTool tidak akan dipanggil secara otomatis saat automaticToolCalling disetel ke false.

// Send a message that triggers a tool call.
val responseMessage = conversation.sendMessage("What's the weather like in London?")

// The model returns a Message with `toolCalls` populated.
if (responseMessage.toolCalls.isNotEmpty()) {
    val toolResponses = mutableListOf<Content.ToolResponse>()
    // There can be multiple tool calls in a single response.
    for (toolCall in responseMessage.toolCalls) {
        println("Model wants to call: ${toolCall.name} with arguments: ${toolCall.arguments}")

        // Execute the tool manually with your own logic. `executeTool` is just an example here.
        val toolResponseJson = executeTool(toolCall.name, toolCall.arguments)

        // Collect tool responses.
        toolResponses.add(Content.ToolResponse(toolCall.name, toolResponseJson))
    }

    // Use Message.tool to create the tool response message.
    val toolResponseMessage = Message.tool(Contents.of(toolResponses))

    // Send the tool response message to the model.
    val finalMessage = conversation.sendMessage(toolResponseMessage)
    println("Final answer: ${finalMessage.text}") // e.g., "The weather in London is 25c."
}

Contoh

Untuk mencoba penggunaan alat, buat clone repo dan jalankan dengan example/ToolMain.kt:

bazel run -c opt //kotlin/java/com/google/ai/edge/litertlm/example:tool -- <abs_model_path>

Penanganan Error

Metode API dapat memunculkan LiteRtLmJniException untuk error dari lapisan native atau pengecualian Kotlin standar seperti IllegalStateException untuk masalah siklus proses. Selalu bungkus panggilan API dalam blok try-catch. Callback onError di MessageCallback juga akan melaporkan error selama operasi asinkron.