Начните работу с LiteRT-LM на Android

Kotlin API LiteRT-LM для Android и JVM (Linux, MacOS, Windows) с такими функциями, как ускорение GPU и NPU , многомодальность и используемые инструменты .

Введение

Вот пример приложения для чата в терминале, созданного с использованием 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) }
      }
    }
  }
}

Демонстрация примера кода на Kotlin.

Чтобы протестировать приведенный выше пример, клонируйте репозиторий и запустите его с помощью файла example/Main.kt :

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

Доступные модели в .litertlm находятся в сообществе HuggingFace LiteRT . В приведенной выше анимации использовалась модель Gemma3-1B-IT .

В качестве примера для Android можно посмотреть приложение Google AI Edge Gallery .

Начало работы с Gradle

Хотя LiteRT-LM разрабатывается с использованием Bazel, мы предоставляем пакеты Maven для пользователей Gradle/Maven.

1. Добавьте зависимость 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")
}

Доступные версии можно найти в Google Maven в пакетах litertlm-android и litertlm-jvm .

Для получения последней версии можно использовать latest.release .

2. Инициализация движка

Engine является точкой входа в API. Инициализируйте его, указав путь к модели и конфигурацию. Не забудьте закрыть движок, чтобы освободить ресурсы.

Примечание: Метод engine.initialize() может занять значительное время (например, до 10 секунд) для загрузки модели. Настоятельно рекомендуется вызывать его в фоновом потоке или сопрограмме, чтобы избежать блокировки потока пользовательского интерфейса.

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

На Android для использования графического процессора приложению необходимо явно запросить необходимые нативные библиотеки, добавив следующее в файл AndroidManifest.xml внутри тега <application> :

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

Для использования бэкенда NPU может потребоваться указать каталог, содержащий библиотеки NPU. На Android, если библиотеки входят в состав вашего приложения, установите его значение равным context.applicationInfo.nativeLibraryDir . Более подробную информацию о нативных библиотеках NPU см. в документации LiteRT-LM NPU .

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

3. Начните разговор

После инициализации движка создайте экземпляр Conversation . Вы можете указать ConversationConfig для настройки его поведения.

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 реализована функция AutoCloseable , поэтому вы можете использовать блок use для автоматического управления ресурсами для разовых или кратковременных бесед:

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

4. Отправка сообщений

Существует три способа отправки сообщений:

  • sendMessage(contents): Message : Синхронный вызов, который блокируется до тех пор, пока модель не вернет полный ответ. Это проще для базовых взаимодействий запрос/ответ.
  • sendMessageAsync(contents, callback) : Асинхронный вызов для потоковой передачи ответов. Это лучше подходит для длительных запросов или когда вы хотите отображать ответ по мере его генерации.
  • sendMessageAsync(contents): Flow<Message> : Асинхронный вызов, возвращающий поток Kotlin для потоковой передачи ответов. Это рекомендуемый подход для пользователей сопрограмм.

Пример синхронного воспроизведения:

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

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

Асинхронный пример с функцией обратного вызова:

Используйте sendMessageAsync для отправки сообщения модели и получения ответов через функцию обратного вызова.

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)

Асинхронный пример с использованием потока:

Используйте sendMessageAsync (без аргумента callback), чтобы отправить сообщение модели и получить ответы через Kotlin Flow.

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. Мультимодальность

Объекты Message могут содержать различные типы Content , включая Text , ImageBytes , ImageFile , а также 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. Определение и использование инструментов

Инструменты можно определить двумя способами:

  1. С использованием функций Kotlin (рекомендуется в большинстве случаев)
  2. С использованием спецификации Open API (полный контроль над спецификацией инструмента и его выполнением).

Определение инструментов с помощью функций Kotlin

Вы можете определять пользовательские функции Kotlin в качестве инструментов, которые модель может вызывать для выполнения действий или получения информации.

Создайте класс, реализующий ToolSet , и аннотируйте методы с помощью @Tool , а параметры — с помощью @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()
    }
}

В фоновом режиме API анализирует эти аннотации и сигнатуру функции для генерации схемы в стиле OpenAPI. Эта схема описывает функциональность инструмента, параметры (включая их типы и описания из @ToolParam ) и тип возвращаемого значения в соответствии с языковой моделью.

Типы параметров

Типы параметров, аннотированных @ToolParam , могут быть String , Int , Boolean , Float , Double или List этих типов (например, List<String> ). Используйте типы, допускающие значение null (например, String? ), чтобы указать параметры, допускающие значение null. Установите значение по умолчанию, чтобы указать, что параметр является необязательным, и укажите это значение в описании в @ToolParam .

Тип возвращаемого значения

Тип возвращаемого значения вашей функции инструмента может быть любым типом Kotlin. Результат будет преобразован в элемент JSON перед отправкой обратно в модель.

  • Типы List преобразуются в массивы JSON.
  • Типы Map преобразуются в объекты JSON.
  • Примитивные типы ( String , Number , Boolean ) преобразуются в соответствующие примитивы JSON.
  • Другие типы данных преобразуются в строки с помощью метода toString() .

Для структурированных данных рекомендуется возвращать Map или класс данных, который будет преобразован в объект JSON.

Определение инструментов с помощью спецификации OpenAPI

В качестве альтернативы вы можете определить инструмент, реализовав класс OpenApiTool и предоставив описание инструмента в виде строки JSON, соответствующей спецификации Open API. Этот метод полезен, если у вас уже есть схема OpenAPI для вашего инструмента или если вам необходим точный контроль над определением инструмента.

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

Инструменты регистрации

Включите экземпляры ваших инструментов в файл 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)

Модель определит, когда следует вызвать инструмент, исходя из хода разговора. Результаты выполнения инструмента автоматически отправляются обратно в модель для генерации окончательного ответа.

Вызов инструмента вручную

По умолчанию вызовы инструментов, генерируемые моделью, автоматически выполняются LiteRT-LM, а результаты выполнения инструментов автоматически отправляются обратно в модель для генерации следующего ответа.

Если вы хотите вручную запускать инструменты и отправлять результаты обратно в модель, вы можете установить automaticToolCalling в ConversationConfig в false .

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

Если вы отключите автоматический вызов инструментов, вам потребуется вручную запускать инструменты и отправлять результаты обратно в модель в коде вашего приложения. Метод execute класса OpenApiTool не будет вызываться автоматически, если automaticToolCalling установлен в 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."
}

Пример

Чтобы протестировать инструмент, клонируйте репозиторий и запустите его с помощью файла example/ToolMain.kt :

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

Обработка ошибок

Методы API могут генерировать исключения LiteRtLmJniException при ошибках нативного уровня или стандартные исключения Kotlin, такие как IllegalStateException , при проблемах жизненного цикла. Всегда оборачивайте вызовы API в блоки try-catch. Коллбэк onError в MessageCallback также будет сообщать об ошибках во время асинхронных операций.