شروع کار با LiteRT-LM در اندروید

رابط برنامه‌نویسی کاتلین LiteRT-LM برای اندروید و JVM (لینوکس، مک‌او‌اس، ویندوز) با ویژگی‌هایی مانند شتاب‌دهی GPU و NPU ، چندوجهی بودن و ابزارهای مورد استفاده .

مقدمه

در اینجا یک نمونه برنامه چت ترمینال ساخته شده با 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) }
      }
    }
  }
}

نسخه آزمایشی برای کد نمونه کاتلین

برای امتحان کردن نمونه بالا، مخزن را کلون کنید و با 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 ساخته شده است.

برای نمونه اندروید، برنامه Google AI Edge Gallery را بررسی کنید.

شروع کار با گریدل

در حالی که LiteRT-LM با Bazel توسعه داده شده است، ما بسته‌های Maven را برای کاربران Gradle/Maven ارائه می‌دهیم.

۱. وابستگی 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 می‌توان برای دریافت آخرین نسخه استفاده کرد.

۲. موتور را مقداردهی اولیه کنید

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

در اندروید، برای استفاده از GPU backend، برنامه باید کتابخانه‌های بومی وابسته را به صراحت با اضافه کردن موارد زیر به 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 backend، ممکن است لازم باشد دایرکتوری حاوی کتابخانه‌های NPU را مشخص کنید. در اندروید، اگر کتابخانه‌ها همراه برنامه شما هستند، آن را روی context.applicationInfo.nativeLibraryDir تنظیم کنید. برای جزئیات بیشتر در مورد کتابخانه‌های بومی NPU به LiteRT-LM NPU مراجعه کنید.

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

۳. یک مکالمه ایجاد کنید

پس از راه‌اندازی اولیه موتور، یک نمونه 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
}

۴. ارسال پیام

سه روش برای ارسال پیام وجود دارد:

  • sendMessage(contents): Message : فراخوانی همزمان که تا زمانی که مدل پاسخ کاملی را برنگرداند، مسدود می‌شود. این برای تعاملات درخواست/پاسخ اولیه ساده‌تر است.
  • sendMessageAsync(contents, callback) : فراخوانی ناهمزمان برای پخش پاسخ‌ها. این روش برای درخواست‌های طولانی مدت یا زمانی که می‌خواهید پاسخ را همزمان با تولید نمایش دهید، بهتر است.
  • sendMessageAsync(contents): Flow<Message> : فراخوانی غیرهمزمان که یک جریان کاتلین را برای جریان‌دهی پاسخ‌ها برمی‌گرداند. این رویکرد برای کاربران Coroutine توصیه می‌شود.

مثال همگام:

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

۵. چندوجهی بودن

اشیاء 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."),
))

۶. تعریف و استفاده از ابزارها

دو روش برای تعریف ابزارها وجود دارد:

  1. با توابع کاتلین (برای اکثر موارد توصیه می‌شود)
  2. با مشخصات Open API (کنترل کامل بر مشخصات و اجرای ابزار)

تعریف ابزارها با توابع کاتلین

شما می‌توانید توابع کاتلین سفارشی را به عنوان ابزارهایی تعریف کنید که مدل می‌تواند برای انجام اقدامات یا دریافت اطلاعات فراخوانی کند.

یک کلاس با پیاده‌سازی 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> ) باشند. از انواع nullable (مثلاً String? ) برای نشان دادن پارامترهای nullable استفاده کنید. یک مقدار پیش‌فرض برای نشان دادن اختیاری بودن پارامتر تنظیم کنید و مقدار پیش‌فرض را در توضیحات @ToolParam ذکر کنید.

نوع بازگشتی

نوع بازگشتی تابع ابزار شما می‌تواند هر نوع کاتلینی باشد. نتیجه قبل از ارسال به مدل، به یک عنصر 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,
    )
)

اگر فراخوانی خودکار ابزار را غیرفعال کنید، باید ابزارها را به صورت دستی اجرا کرده و نتایج را به مدل در کد برنامه خود ارسال کنید. وقتی automaticToolCalling روی false تنظیم شده باشد، متد execute از OpenApiTool به طور خودکار فراخوانی نخواهد شد.

// 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 می‌توانند برای خطاهای لایه native، LiteRtLmJniException یا برای مشکلات چرخه عمر، استثناهای استاندارد Kotlin مانند IllegalStateException را ارسال کنند. همیشه فراخوانی‌های API را در بلوک‌های try-catch قرار دهید. تابع فراخوانی onError در MessageCallback نیز خطاها را در حین عملیات ناهمزمان گزارش می‌دهد.