Erste Schritte mit LiteRT-LM unter Android

Die Kotlin API von LiteRT-LM für Android und JVM (Linux, macOS, Windows) mit Funktionen wie GPU- und NPU-Beschleunigung, Multimodalität und Tool-Nutzung.

Einführung

Hier ist eine Beispiel-Terminal-Chat-App, die mit der Kotlin API erstellt wurde:

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 für den Kotlin-Beispielcode

Wenn Sie das obige Beispiel ausprobieren möchten, klonen Sie das Repository und führen Sie es mit example/Main.kt aus:

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

Verfügbare .litertlm Modelle finden Sie in der HuggingFace LiteRT Community. In der obigen Animation wurde Gemma3-1B-IT verwendet.

Ein Android-Beispiel finden Sie in der Google AI Edge Gallery App (verfügbar bei Google Play).

Erste Schritte mit Gradle

LiteRT-LM wird mit Bazel entwickelt, aber wir stellen die Maven-Pakete für Gradle- oder Maven-Nutzer bereit.

1. Gradle-Abhängigkeit hinzufügen

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")
}

Die verfügbaren Versionen finden Sie in Google Maven unter litertlm-android und litertlm-jvm.

Mit latest.release können Sie die neueste Version abrufen.

2. Engine initialisieren

Die Engine ist der Einstiegspunkt zur API. Initialisieren Sie sie mit dem Modellpfad und der Konfiguration. Denken Sie daran, die Engine zu schließen, um Ressourcen freizugeben.

Hinweis:Das Laden des Modells mit der Methode engine.initialize() kann viel Zeit in Anspruch nehmen (z.B. bis zu 10 Sekunden). Es wird dringend empfohlen, diese Methode in einem Hintergrundthread oder einer Coroutine aufzurufen, um zu verhindern, dass der UI-Thread blockiert wird.

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()

Wenn Sie unter Android das GPU-Backend verwenden möchten, muss die App die abhängigen nativen Bibliotheken explizit anfordern. Fügen Sie dazu im Tag <application> in Ihrem AndroidManifest.xml Folgendes hinzu:

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

Wenn Sie das NPU -Backend verwenden möchten, müssen Sie möglicherweise das Verzeichnis angeben, das die NPU-Bibliotheken enthält. Wenn die Bibliotheken unter Android mit Ihrer App gebündelt sind, legen Sie es auf context.applicationInfo.nativeLibraryDir fest. Weitere Informationen zu den nativen NPU-Bibliotheken finden Sie unter LiteRT-LM NPU.

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

3. Unterhaltung erstellen

Erstellen Sie nach der Initialisierung der Engine eine Conversation-Instanz. Sie können eine ConversationConfig angeben, um das Verhalten anzupassen.

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 implementiert AutoCloseable. Sie können also den use-Block für die automatische Ressourcenverwaltung für einmalige oder kurzlebige Unterhaltungen verwenden:

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

4. Nachrichten senden

Es gibt drei Möglichkeiten, Nachrichten zu senden:

  • sendMessage(contents): Message: Synchroner Aufruf, der blockiert wird, bis das Modell eine vollständige Antwort zurückgibt. Diese Methode ist einfacher für grundlegende Anfragen und Antworten.
  • sendMessageAsync(contents, callback): Asynchroner Aufruf für Streamingantworten. Diese Methode ist besser für zeitaufwendige Anfragen oder wenn Sie die Antwort anzeigen möchten, während sie generiert wird.
  • sendMessageAsync(contents): Flow<Message>: Asynchroner Aufruf, der einen Kotlin-Flow für Streamingantworten zurückgibt. Dies ist der empfohlene Ansatz für Coroutine-Nutzer.

Synchrones Beispiel :

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

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

Asynchrones Beispiel mit Callback :

Verwenden Sie sendMessageAsync, um eine Nachricht an das Modell zu senden und Antworten über einen Callback zu erhalten.

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)

Asynchrones Beispiel mit Flow :

Verwenden Sie sendMessageAsync (ohne das Callback-Argument), um eine Nachricht an das Modell zu senden und Antworten über einen Kotlin-Flow zu erhalten.

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. Multimodalität

Message -Objekte können verschiedene Arten von Content enthalten, darunter Text, ImageBytes, ImageFile, AudioBytes und AudioFile.

// Initialize the `visionBackend`, `audioBackend`, or both
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. Tools definieren und verwenden

Es gibt zwei Möglichkeiten, Tools zu definieren:

  1. Mit Kotlin-Funktionen (in den meisten Fällen empfohlen)
  2. Mit der OpenAPI-Spezifikation (vollständige Kontrolle über die Tool-Spezifikation und -Ausführung)

Tools mit Kotlin-Funktionen definieren

Sie können benutzerdefinierte Kotlin-Funktionen als Tools definieren, die das Modell aufrufen kann, um Aktionen auszuführen oder Informationen abzurufen.

Erstellen Sie eine Klasse, die ToolSet implementiert, und versehen Sie Methoden mit @Tool und Parametern mit @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()
    }
}

Im Hintergrund prüft die API diese Anmerkungen und die Funktionssignatur, um ein Schema im OpenAPI-Stil zu generieren. Dieses Schema beschreibt die Funktionalität, die Parameter (einschließlich ihrer Typen und Beschreibungen aus @ToolParam) und den Rückgabetyp des Tools für das Sprachmodell.

Parametertypen

Die Typen für Parameter, die mit @ToolParam annotiert sind, können String, Int, Boolean, Float, Double, oder eine List dieser Typen sein (z.B. List<String>). Verwenden Sie Nullable-Typen (z.B. String?), um Nullable-Parameter anzugeben. Legen Sie einen Standardwert fest, um anzugeben, dass der Parameter optional ist, und erwähnen Sie den Standardwert in der Beschreibung in @ToolParam.

Rückgabetyp

Der Rückgabetyp Ihrer Tool-Funktion kann ein beliebiger Kotlin-Typ sein. Das Ergebnis wird in ein JSON-Element konvertiert, bevor es an das Modell zurückgesendet wird.

  • List-Typen werden in JSON-Arrays konvertiert.
  • Map-Typen werden in JSON-Objekte konvertiert.
  • Primitive Typen (String, Number, Boolean) werden in das entsprechende JSON-Primitiv konvertiert.
  • Andere Typen werden mit der Methode toString() in Strings konvertiert.

Für strukturierte Daten wird empfohlen, Map oder eine Datenklasse zurückzugeben, die in ein JSON-Objekt konvertiert wird.

Tools mit der OpenAPI-Spezifikation definieren

Alternativ können Sie ein Tool definieren, indem Sie die Klasse OpenApiTool implementieren und die Beschreibung des Tools als JSON-String gemäß der OpenAPI-Spezifikation angeben. Diese Methode ist nützlich, wenn Sie bereits ein OpenAPI-Schema für Ihr Tool haben oder wenn Sie die Definition des Tools detailliert steuern müssen.

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 or deserializer and
        // execute the tool.

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

Tools registrieren

Fügen Sie Instanzen Ihrer Tools in die ConversationConfig ein.

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)

Das Modell entscheidet anhand der Unterhaltung, wann das Tool aufgerufen werden soll. Die Ergebnisse der Tool-Ausführung werden automatisch an das Modell zurückgesendet, um die endgültige Antwort zu generieren.

Manuelle Tool-Aufrufe

Standardmäßig werden Tool-Aufrufe, die vom Modell generiert werden, automatisch von LiteRT-LM ausgeführt. Die Ergebnisse der Tool-Ausführung werden automatisch an das Modell zurückgesendet, um die nächste Antwort zu generieren.

Wenn Sie Tools manuell ausführen und Ergebnisse an das Modell zurücksenden möchten, können Sie automaticToolCalling in ConversationConfig auf false setzen.

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

Wenn Sie automatische Tool-Aufrufe deaktivieren, müssen Sie Tools manuell ausführen und Ergebnisse in Ihrem Anwendungscode an das Modell zurücksenden. Die Methode execute von OpenApiTool wird nicht automatisch aufgerufen, wenn automaticToolCalling auf false gesetzt ist.

// 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."
}

Beispiel

Wenn Sie die Tool-Nutzung ausprobieren möchten, klonen Sie das Repository und führen Sie es mit example/ToolMain.kt aus:

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

Fehlerbehandlung

API-Methoden können LiteRtLmJniException für Fehler aus der nativen Ebene oder Standard-Kotlin-Ausnahmen wie IllegalStateException für Lebenszyklusprobleme auslösen. Umschließen Sie API-Aufrufe immer mit Try-Catch-Blöcken. Der onError-Callback in MessageCallback meldet auch Fehler bei asynchronen Vorgängen.