Bắt đầu sử dụng LiteRT-LM trên Android

API Kotlin của LiteRT-LM cho AndroidJVM (Linux, macOS, Windows) với các tính năng như tăng tốc GPU và NPU, đa phương thức, và sử dụng công cụ.

Giới thiệu

Sau đây là một ứng dụng trò chuyện trên thiết bị đầu cuối mẫu được xây dựng bằng 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) }
      }
    }
  }
}

Bản minh hoạ cho mã mẫu Kotlin

Để dùng thử mẫu ở trên, hãy sao chép kho lưu trữ và chạy bằng example/Main.kt:

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

Các mô hình .litertlm có sẵn nằm trên Cộng đồng HuggingFace LiteRT. Ảnh động ở trên đang sử dụng Gemma3-1B-IT.

Đối với mẫu Android, hãy xem ứng dụng Google AI Edge Gallery (có trên Google Play).

Bắt đầu sử dụng Gradle

Mặc dù LiteRT-LM được phát triển bằng Bazel, nhưng chúng tôi cung cấp các gói Maven cho người dùng Gradle hoặc Maven.

1. Thêm phần phụ thuộc vào 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")
}

Bạn có thể tìm thấy các phiên bản có sẵn trên Google Maven trong litertlm-androidlitertlm-jvm.

Bạn có thể sử dụng latest.release để nhận bản phát hành mới nhất.

2. Khởi chạy Engine

Engine là điểm truy cập vào API. Khởi chạy bằng đường dẫn và cấu hình mô hình. Hãy nhớ đóng engine để giải phóng tài nguyên.

Lưu ý: Phương thức engine.initialize() có thể mất một khoảng thời gian đáng kể (ví dụ: tối đa 10 giây) để tải mô hình. Bạn nên gọi phương thức này trên một luồng nền hoặc coroutine để tránh chặn luồng giao diện người dùng.

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

Trên Android, để sử dụng phần phụ trợ GPU, ứng dụng cần yêu cầu các thư viện gốc phụ thuộc một cách rõ ràng bằng cách thêm nội dung sau vào AndroidManifest.xml bên trong thẻ <application> tag:

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

Để sử dụng phần phụ trợ NPU, bạn có thể cần chỉ định thư mục chứa các thư viện NPU. Trên Android, nếu các thư viện được gói cùng với ứng dụng, hãy đặt thành context.applicationInfo.nativeLibraryDir. Xem NPU LiteRT-LM để biết thêm thông tin chi tiết về các thư viện gốc NPU.

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

3. Tạo cuộc trò chuyện

Sau khi khởi chạy engine, hãy tạo một thực thể Conversation. Bạn có thể cung cấp ConversationConfig để tuỳ chỉnh hành vi của thực thể này.

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 triển khai AutoCloseable, vì vậy, bạn có thể sử dụng khối use để tự động quản lý tài nguyên cho các cuộc trò chuyện một lần hoặc ngắn hạn:

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

4. Gửi tin nhắn

Có 3 cách để gửi tin nhắn:

  • sendMessage(contents): Message: Lệnh gọi đồng bộ chặn cho đến khi mô hình trả về phản hồi hoàn chỉnh. Cách này đơn giản hơn cho các tương tác cơ bản về yêu cầu và phản hồi.
  • sendMessageAsync(contents, callback): Lệnh gọi không đồng bộ để truyền trực tuyến phản hồi. Cách này phù hợp hơn cho các yêu cầu chạy trong thời gian dài hoặc khi bạn muốn hiển thị phản hồi khi phản hồi đang được tạo.
  • sendMessageAsync(contents): Flow<Message>: Lệnh gọi không đồng bộ trả về một Luồng Kotlin để truyền trực tuyến phản hồi. Đây là phương pháp được đề xuất cho người dùng Coroutine.

Ví dụ đồng bộ:

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

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

Ví dụ không đồng bộ với lệnh gọi lại:

Sử dụng sendMessageAsync để gửi tin nhắn đến mô hình và nhận phản hồi thông qua lệnh gọi lại.

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)

Ví dụ không đồng bộ với Flow:

Sử dụng sendMessageAsync (không có đối số lệnh gọi lại) để gửi tin nhắn đến mô hình và nhận phản hồi thông qua Luồng 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. Đa phương thức

Các đối tượng Message có thể chứa nhiều loại Content, bao gồm Text, ImageBytes, ImageFileAudioBytes, 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. Xác định và sử dụng công cụ

Có 2 cách để xác định công cụ:

  1. Với các hàm Kotlin (được đề xuất cho hầu hết các trường hợp)
  2. Với thông số kỹ thuật Open API (kiểm soát hoàn toàn thông số kỹ thuật và quá trình thực thi công cụ)

Xác định công cụ bằng các hàm Kotlin

Bạn có thể xác định các hàm Kotlin tuỳ chỉnh làm công cụ mà mô hình có thể gọi để thực hiện các hành động hoặc tìm nạp thông tin.

Tạo một lớp triển khai ToolSet và chú thích các phương thức bằng @Tool và các tham số bằng @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()
    }
}

Ở phía sau, API sẽ kiểm tra các chú giải này và chữ ký hàm để tạo một lược đồ kiểu OpenAPI. Lược đồ này mô tả chức năng, các tham số (bao gồm cả loại và nội dung mô tả của chúng từ @ToolParam) và kiểu dữ liệu trả về của công cụ cho mô hình ngôn ngữ.

Các loại tham số

Các loại tham số được chú thích bằng @ToolParam có thể là String, Int, Boolean, Float, Double hoặc List của các loại này (ví dụ: List<String>). Sử dụng các loại có thể rỗng (ví dụ: String?) để cho biết các tham số có thể rỗng. Đặt giá trị mặc định để cho biết tham số là không bắt buộc và đề cập đến giá trị mặc định trong nội dung mô tả trong @ToolParam.

Loại trả về

Kiểu dữ liệu trả về của hàm công cụ có thể là bất kỳ kiểu Kotlin nào. Kết quả sẽ được chuyển đổi thành một phần tử JSON trước khi được gửi lại cho mô hình.

  • Các loại List được chuyển đổi thành mảng JSON.
  • Các loại Map được chuyển đổi thành đối tượng JSON.
  • Các loại nguyên thuỷ (String, Number, Boolean) được chuyển đổi thành nguyên thuỷ JSON tương ứng.
  • Các loại khác được chuyển đổi thành chuỗi bằng phương thức toString().

Đối với dữ liệu có cấu trúc, bạn nên trả về Map hoặc một lớp dữ liệu sẽ được chuyển đổi thành đối tượng JSON.

Xác định công cụ bằng thông số kỹ thuật OpenAPI

Ngoài ra, bạn có thể xác định một công cụ bằng cách triển khai lớp OpenApiTool và cung cấp nội dung mô tả của công cụ dưới dạng chuỗi JSON tuân thủ thông số kỹ thuật Open API. Phương thức này hữu ích nếu bạn đã có lược đồ OpenAPI cho công cụ hoặc nếu bạn cần kiểm soát chi tiết định nghĩa của công cụ.

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

Đăng ký công cụ

Đưa các thực thể của công cụ vào 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)

Mô hình sẽ quyết định thời điểm gọi công cụ dựa trên cuộc trò chuyện. Kết quả từ quá trình thực thi công cụ sẽ tự động được gửi lại cho mô hình để tạo phản hồi cuối cùng.

Gọi công cụ theo cách thủ công

Theo mặc định, các lệnh gọi công cụ do mô hình tạo sẽ tự động được LiteRT-LM thực thi và kết quả từ quá trình thực thi công cụ sẽ tự động được gửi lại cho mô hình để tạo phản hồi tiếp theo.

Nếu muốn thực thi công cụ theo cách thủ công và gửi kết quả trở lại mô hình, bạn có thể đặt automaticToolCalling trong ConversationConfig thành false.

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

Nếu tắt tính năng gọi công cụ tự động, bạn sẽ cần thực thi công cụ theo cách thủ công và gửi kết quả trở lại mô hình trong mã xử lý ứng dụng. Phương thức execute của OpenApiTool sẽ không được gọi tự động khi automaticToolCalling được đặt thành 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."
}

Ví dụ:

Để dùng thử tính năng sử dụng công cụ, hãy sao chép kho lưu trữ và chạy bằng example/ToolMain.kt:

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

Xử lý lỗi

Các phương thức API có thể gửi LiteRtLmJniException cho các lỗi từ lớp gốc hoặc các ngoại lệ Kotlin tiêu chuẩn như IllegalStateException cho các vấn đề về vòng đời. Luôn gói các lệnh gọi API trong các khối try-catch. Lệnh gọi lại onError trong MessageCallback cũng sẽ báo cáo lỗi trong các hoạt động không đồng bộ.