L'API Kotlin di LiteRT-LM per Android e JVM (Linux, macOS, Windows) con funzionalità come accelerazione GPU e NPU, multimodalità e utilizzo degli strumenti.
Introduzione
Ecco un'app di chat terminale di esempio creata con l'API Kotlin:
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) }
}
}
}
}

Per provare l'esempio precedente, clona il repository ed esegui con example/Main.kt:
bazel run -c opt //kotlin/java/com/google/ai/edge/litertlm/example:main -- <abs_model_path>
I modelli .litertlm disponibili si trovano nella
community HuggingFace LiteRT. L'animazione
sopra utilizzava
Gemma3-1B-IT.
Per l'esempio Android, dai un'occhiata all'app galleria Google AI Edge.
Introduzione a Gradle
Sebbene LiteRT-LM sia sviluppato con Bazel, forniamo i pacchetti Maven per gli utenti di Gradle/Maven.
1. Aggiungi la dipendenza 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")
}
Puoi trovare le versioni disponibili su Google Maven in litertlm-android e litertlm-jvm.
latest.release può essere utilizzato per ottenere l'ultima release.
2. Inizializzare il motore
Engine è il punto di ingresso dell'API. Inizializzalo con il percorso del modello
e la configurazione. Ricordati di chiudere il motore per rilasciare le risorse.
Nota:il caricamento del modello con il metodo engine.initialize() può richiedere molto tempo
(ad esempio, fino a 10 secondi). Ti consigliamo vivamente di chiamare
questo metodo su un thread o una coroutine in background per evitare di bloccare il 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()
Su Android, per utilizzare il backend GPU, l'app deve richiedere le librerie native
dipendenti in modo esplicito aggiungendo quanto segue a
AndroidManifest.xml all'interno del tag <application>:
<application>
<uses-native-library android:name="libvndksupport.so" android:required="false"/>
<uses-native-library android:name="libOpenCL.so" android:required="false"/>
</application>
Per utilizzare il backend NPU, potrebbe essere necessario specificare la directory contenente
le librerie NPU. Su Android, se le librerie sono raggruppate con la tua app, impostalo su context.applicationInfo.nativeLibraryDir. Per maggiori dettagli sulle librerie native della NPU, consulta NPU LiteRT-LM.
val engineConfig = EngineConfig(
modelPath = modelPath,
backend = Backend.NPU(nativeLibraryDir = context.applicationInfo.nativeLibraryDir)
)
3. Creare una conversazione
Una volta inizializzato il motore, crea un'istanza Conversation. Puoi
fornire un ConversationConfig per personalizzarne il comportamento.
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 implementa AutoCloseable, quindi puoi utilizzare il blocco use per
la gestione automatica delle risorse per conversazioni una tantum o di breve durata:
engine.createConversation(conversationConfig).use { conversation ->
// Interact with the conversation
}
4. Invio di messaggi
Esistono tre modi per inviare messaggi:
sendMessage(contents): Message: chiamata sincrona che blocca fino a quando il modello non restituisce una risposta completa. Questa opzione è più semplice per le interazioni richiesta/risposta di base.sendMessageAsync(contents, callback): Chiamata asincrona per le risposte di streaming. Questa opzione è più adatta per le richieste di lunga durata o quando vuoi visualizzare la risposta man mano che viene generata.sendMessageAsync(contents): Flow<Message>: chiamata asincrona che restituisce un flusso Kotlin per lo streaming delle risposte. Questo è l'approccio consigliato per gli utenti di Coroutine.
Esempio sincrono:
import com.google.ai.edge.litertlm.Content
import com.google.ai.edge.litertlm.Message
print(conversation.sendMessage("What is the capital of France?"))
Esempio asincrono con callback:
Utilizza sendMessageAsync per inviare un messaggio al modello e ricevere risposte
tramite un 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)
Esempio asincrono con flusso:
Utilizza sendMessageAsync (senza l'argomento di callback) per inviare un messaggio al modello
e ricevere risposte tramite un flusso 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-Modality
Gli oggetti Message possono contenere diversi tipi di Content, tra cui Text,
ImageBytes, ImageFile e 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. Definizione e utilizzo degli strumenti
Esistono due modi per definire gli strumenti:
- Con le funzioni Kotlin (consigliate nella maggior parte dei casi)
- Con la specifica Open API (controllo completo della specifica e dell'esecuzione dello strumento)
Definizione degli strumenti con le funzioni Kotlin
Puoi definire funzioni Kotlin personalizzate come strumenti che il modello può chiamare per eseguire azioni o recuperare informazioni.
Crea una classe che implementi ToolSet e annota i metodi con @Tool e i parametri con @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()
}
}
Dietro le quinte, l'API esamina queste annotazioni e la firma della funzione
per generare uno schema in stile OpenAPI. Questo schema descrive la funzionalità, i parametri (inclusi i tipi e le descrizioni di @ToolParam) e il tipo restituito dello strumento al modello linguistico.
Tipi di parametri
I tipi per i parametri annotati con @ToolParam possono essere String, Int,
Boolean, Float, Double o un List di questi tipi (ad es. List<String>).
Utilizza tipi che ammettono valori Null (ad es. String?) per indicare i parametri che accettano valori nulli. Imposta un
valore predefinito per indicare che il parametro è facoltativo e menziona il
valore predefinito nella descrizione in @ToolParam.
Tipo restituito
Il tipo restituito della funzione dello strumento può essere qualsiasi tipo Kotlin. Il risultato verrà convertito in un elemento JSON prima di essere inviato di nuovo al modello.
- I tipi
Listvengono convertiti in array JSON. - I tipi
Mapvengono convertiti in oggetti JSON. - I tipi primitivi (
String,Number,Boolean) vengono convertiti nel primitivo JSON corrispondente. - Gli altri tipi vengono convertiti in stringhe con il metodo
toString().
Per i dati strutturati, è consigliabile restituire Map o una classe di dati che verrà convertita in un oggetto JSON.
Definizione degli strumenti con la specifica OpenAPI
In alternativa, puoi definire uno strumento implementando la classe OpenApiTool e
fornendo la descrizione dello strumento come stringa JSON conforme alla specifica
Open API. Questo metodo è utile se disponi già di uno schema OpenAPI per
il tuo strumento o se hai bisogno di un controllo granulare sulla definizione dello strumento.
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}"""
}
}
Strumenti di registrazione
Includi istanze dei tuoi strumenti nel 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)
Il modello deciderà quando chiamare lo strumento in base alla conversazione. I risultati dell'esecuzione dello strumento vengono inviati automaticamente al modello per generare la risposta finale.
Chiamata manuale dello strumento
Per impostazione predefinita, le chiamate di strumenti generate dal modello vengono eseguite automaticamente da LiteRT-LM e i risultati dell'esecuzione dello strumento vengono inviati automaticamente al modello per generare la risposta successiva.
Se vuoi eseguire manualmente gli strumenti e inviare i risultati al modello, puoi
impostare automaticToolCalling in ConversationConfig su false.
val conversation = engine.createConversation(
ConversationConfig(
tools = listOf(
tool(SampleOpenApiTool()),
),
automaticToolCalling = false,
)
)
Se disattivi la chiamata automatica degli strumenti, dovrai eseguire manualmente gli strumenti
e inviare i risultati al modello nel codice dell'applicazione. Il metodo execute
di OpenApiTool non verrà chiamato automaticamente quando
automaticToolCalling è impostato su 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."
}
Esempio
Per provare a utilizzare lo strumento, clona il repository ed esegui con example/ToolMain.kt:
bazel run -c opt //kotlin/java/com/google/ai/edge/litertlm/example:tool -- <abs_model_path>
Gestione degli errori
I metodi API possono generare LiteRtLmJniException per gli errori del livello nativo o
eccezioni Kotlin standard come IllegalStateException per i problemi del ciclo di vita.
Esegui sempre il wrapping delle chiamate API nei blocchi try-catch. Il callback onError in
MessageCallback segnalerà anche gli errori durante le operazioni asincrone.