با گردشهای کاری عاملی دروندستگاهی ML Kit (com.google.mlkit:genai-agent،
که ازطریق برنامه پیشنمای توسعهدهندگان نرمافزار AICore دردسترس است)، میتوانید
عاملهای مکالمهای چندنوبتی خودکار با پشتیبانی Gemini Nano و
سرویس سیستم Android AICore بسازید.
کلاس AgentRunner که بر پایه کلاس GenerativeModel ساخته شده است و
دسترسی مستقیم به مدل را فراهم میکند،
حلقههای عاملی کامل را خودکارسازی میکند: برنامهریزی کنشهای چندمرحلهای، فراخوانی ابزارهای تایپشده (مانند
پرسوجو از مخزن برنامه، فراخوانی میانای برنامهسازی کاربردی پسبست، یا خواندن پایگاه داده محلی)،
ارزیابی نتایج ابزار، و مدیریت بافت مکالمه بهصورت محلی در دستگاه
با تأخیر شبکه صفر و حریم خصوصی کامل کاربر. درحالیکه کلاس GenerativeModel
از سابقه چندنوبتی مدیریتشده توسط کارخواه بااستفاده از شیء List<Content> (با
نقشهای USER و MODEL) و مقدار sessionId پشتیبانی میکند، کلاس AgentRunner
جلسههای چندنوبتی مدیریتشده (بااستفاده از میانای AgentSession) ارائه میدهد که
سابقه مکالمه درونحافظهای را بهطور خودکار درطول نوبتها ضبط میکند تا
مجبور نباشید ترانویسیها را بهصورت دستی مدیریت کنید. دستورالعملهای سیستم و اعلامیههای ابزار ایستا نیز بهطور خودکار در دستگاه ذخیره میشوند، که این امر زمان تأخیر تا اولین کد را بدون نیاز به پیکربندی اضافی کاهش میدهد.
اجزای اصلی
- کلاس
Agent: دستورالعملها، ابزارهای دردسترس، و حفاظهای اجرا را بااستفاده از روشAgent.builder()اعلام میکند. بهطور خودکار بهmaxTurns = 10پیشفرض میشود. - کلاس
AgentRunner: بر روی کلاسGenerativeModelساخته شده است و حلقههای استدلال خودکار و جلسات مدیریتشده را با مدلهای دروندستگاهی هماهنگ میکند. Main-safe by default onDispatchers.IO, withtemperaturedefaulting tonull(which defers to the model's default; passtemperature = 0.0ffor greedy decoding). - میانای
AgentSession: دسته جلسه مکالمه چندنوبتی با دامنه محدود که بااستفاده از روشrunner.getOrCreateSession(sessionId)بهدست آمده است و سابقه مکالمه در حافظه را بهطور خودکار در نوبتها با روشهایrunAsync(content)وclose()ضبط میکند. - میانای
AgentEvent: جریان واکنشی KotlinFlowکه نشانهای نوشتاری و بهروزرسانیهای ابزار را منتشر میکند. - کلاس
AgentTool: توابع تایپشده قابلفراخوانی توسط مدل، تعریفشده بااستفاده از Android AppFunctions (@AppFunctionDeclaration). - کلاس
GenAiException: سلسلهمراتب استثنای تایپشده (com.google.mlkit.genai.common.GenAiException) که حالتهای خرابی را نشان میدهد مانندMAX_TURNS_EXCEEDED،NOT_AVAILABLE، یاBUSY.
مهارتهای Android
مشاهده در GitHubاستفاده از ML Kit Agent API
android skills add mlkit-agent-apiپیشنیازها و وابستگیها
قبلاز ادغام Agent API، مطمئن شوید محیط توسعه و پیکربندی Gradle شما شرایط زیر را برآورده میکند.
الزامات پلاتفرم و سختافزار
- نسخه Android: Android 14 (سطح میانای برنامهسازی کاربردی ۳۴) یا بالاتر که در برنامه پیشنمای توسعهدهندگان نرمافزار AICore ثبتنام شده باشد.
- دستگاههای پشتیبانیشده: برای مشاهده فهرست دستگاههای پشتیبانیشده و نسخههای مدل Gemini Nano آنها، به پشتیبانی از دستگاه عامل و تولید سفارشی مراجعه کنید.
- حداقل کیت توسعه نرمافزار:
minSdk 26یا بالاتر. - Kotlin: Kotlin نسخه ۲.۳ یا بالاتر.
پیکربندی Gradle
وابستگی genai-agent را به فایل build.gradle.kts واحدتان اضافه کنید:
dependencies {
// Gemini Nano agentic workflows
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
// Optional: Only required when defining tools using Android AppFunctions
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}
درحالیکه خود genai-agent به پردازشگر گزارمان یا افزونه کامپایلر نیاز ندارد، تعریف ابزارها با androidx.appfunctions به افزونه KSP appfunctions-compiler نیاز دارد.
شروع سریع: مقداردهی اولیه عامل
نمونه کد زیر نحوه نمونهسازی عامل و اجراکننده دروندستگاهی را نشان میدهد:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
// Configure the agent with system instructions
val agent = Agent.builder()
.setInstructions("You are a helpful on-device assistant.")
.build()
// Create the runner to execute prompts
val runner = AgentRunner(agent)
تعریف کردن ابزارها
مدل میتواند ابزارهای تایپشده را برای پُرسمان دادههای برنامه، برقراری ارتباط با سرویسهای پسزمینه، یا راهاندازی کنشهای درونبرنامه فراخوانی کند. ابزارها بهصورت اعلانی بااستفاده از
Android AppFunctions (androidx.appfunctions) تعریف میشوند:
package com.example.ai.agent.tools
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@AppFunctionSerializable
data class OrderDetails(
val orderId: String,
val status: String,
val eta: String,
)
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
/**
* Fetches details and delivery status for an in-app order.
*
* @param orderId The alphanumeric order identifier
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fetch_order_details(
orderId: String
): OrderDetails = withContext(Dispatchers.IO) {
// Query local database or call your backend API
OrderDetails(
orderId = orderId,
status = "Out for delivery",
eta = "2:30 PM",
)
}
}
سرویس تولیدشده را در فایل AndroidManifest.xml برنامهتان ثبت کنید:
<service
android:name=".OrderAppFunctionService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
android:exported="true">
<property
android:name="android.app.appfunctions.schema"
android:value="app_functions_schema.xsd" />
<property
android:name="android.app.appfunctions.v2"
android:value="order_app_functions.xml" />
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
محدود کردن دسترسی تماسگیرنده با ثابت ACCESS_LEVEL_SELF
AppFunctions لازم نیست برای سیستم یا تماسگیرندگان خارجی عمومی باشد. در Android، کنترل دسترسی ازطریق سطوح دسترسی تماسگیرنده انجام میشود، نه ازطریق بررسی هویت تماسگیرنده.
بهطور پیشفرض (بااستفاده از ثابت ACCESS_LEVEL_ANDROID_TRUSTED)، دستیارهای دروندستگاهی دارای گواهی پلاتفرم که اجازه سیستم EXECUTE_APP_FUNCTIONS را دارند میتوانند AppFunctions را فراخوانی کنند. اگر ابزار شما صرفاً برای
عامل داخلی برنامهتان درنظر گرفته شده است (برای مثال، مدیریت سبدهای خرید کاربر یا رکوردهای پایگاه داده خصوصی)،
دسترسی فراخواننده را بااستفاده از ثابت ACCESS_LEVEL_SELF محدود کنید:
import androidx.appfunctions.AppFunctionAccessLevel
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.ExperimentalAppFunctionsApi
import androidx.appfunctions.metadata.AppFunctionMetadata
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
/**
* Cancels an order. Restricted strictly to this app's internal agent.
*/
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
fun cancel_order(
orderId: String
): String {
// Only your app's internal agent can invoke this function
return "Order $orderId cancelled successfully."
}
}
وقتی با ثابت ACCESS_LEVEL_SELF محدود شود، پلاتفرم سطوح خارجی و دستیارهای سیستم را از کاوش یا اجرای تابع روی IPC مسدود میکند.
پیکربندی کردن عامل
نمونه Agent را با دستورالعملهای سیستم پیکربندی کنید و AppFunctions را فعال کنید،
سپس نمونه AgentRunner را مقداردهی اولیه کنید:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
// Configure the agent with system instructions and enable AppFunctions
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions(
"You are a helpful assistant for looking up store orders. " +
"Use available tools to fetch order information."
)
.enableAppFunctions(true) // Automatically discovers all registered AppFunctions in your app
.build()
// Initialize the runner
val runner = AgentRunner(agent)
درصورت تمایل، روش addAppFunctionIds("fetch_order_details", "cancel_order") را فراخوانی کنید تا محدوده کاوش را بهجای نمایش همه عملکردهای درونبرنامه، دقیقاً به شناسههای AppFunction خاص محدود کنید.
پیشفرضهای عامل
- محدودیتهای نوبت: کلاس
Agent.Builderبهطور خودکار بهعنوان شرط پایان پیشفرض میشودmaxTurns = 10تا از حلقههای فراخوانی ابزار بینهایت جلوگیری شود. برای گردشهای کاری استاندارد مکالمهای یا فراخوانی ابزار، نیازی نیست که روشsetMaxTurns(10)را بهصورت دستی فراخوانی کنید. - رمزگشایی حریصانه قطعی: کلاس
AgentRunnerپارامتر پیشفرضtemperatureرا رویnullتنظیم میکند و به پیشفرض مدل زیربنایی ارجاع میدهد. برای انتخاب قطعی ابزار و رعایت طرحواره JSON در دستگاه،temperature = 0.0f(temperature) را به سازندهAgentRunnerارسال کنید. از تنظیم دماهای سبک ابری (> 0.4f) که خطر کژپنداری و تخریب طرحواره را در مدلهای تلفن همراه افزایش میدهد خودداری کنید.
اجرای چندنوبتی و رویدادهای جاریسازی
بااستفاده از روش runner.getOrCreateSession(sessionId) در کلاس Android ViewModel، نمونه AgentSession را دریافت کنید. جلسه یک جاریسازی Flow<AgentEvent> را برمیگرداند که کدهای نوشتاری همزمان و بهروزرسانیهای اجرای ابزار را منتشر میکند:
package com.example.ai.agent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.genai.agent.AgentEvent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentSession
import com.google.mlkit.genai.common.GenAiException
import com.google.mlkit.genai.prompt.Content
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
data class AgentUiState(
val messages: List<String> = emptyList(),
val currentResponse: String = "",
val activeToolStatus: String? = null,
val isLoading: Boolean = false,
val errorMessage: String? = null
)
class ChatViewModel(
private val runner: AgentRunner,
private val sessionId: String = "chat_session"
) : ViewModel() {
// Scoped session preserves conversation history across configuration changes (screen rotations)
private val session: AgentSession = runner.getOrCreateSession(sessionId)
private val _uiState = MutableStateFlow(AgentUiState())
val uiState: StateFlow<AgentUiState> = _uiState.asStateFlow()
fun sendMessage(userText: String) {
_uiState.update {
it.copy(
messages = it.messages + userText,
currentResponse = "",
activeToolStatus = null,
isLoading = true,
errorMessage = null
)
}
viewModelScope.launch {
val content = Content.builder().text(userText).build()
session.runAsync(content)
.catch { e ->
val message = when {
e is GenAiException && e.errorCode == GenAiException.ErrorCode.MAX_TURNS_EXCEEDED ->
"Agent exceeded the maximum reasoning turns allowed."
e is GenAiException && e.errorCode == GenAiException.ErrorCode.NOT_AVAILABLE ->
"On-device AI is currently unavailable."
e is GenAiException && e.errorCode == GenAiException.ErrorCode.BUSY ->
"On-device AI is busy. Please try again."
else -> e.localizedMessage ?: "Agent error"
}
_uiState.update {
it.copy(
isLoading = false,
activeToolStatus = null,
errorMessage = message
)
}
}
.collect { event ->
when (event) {
is AgentEvent.TextMessage -> {
_uiState.update {
it.copy(
currentResponse = if (event.isPartial) {
it.currentResponse + event.text
} else {
event.text.ifEmpty { it.currentResponse }
},
activeToolStatus = null
)
}
}
is AgentEvent.FunctionCall -> {
_uiState.update {
it.copy(activeToolStatus = "Executing ${event.name}...")
}
}
is AgentEvent.FunctionResponse -> {
_uiState.update {
it.copy(activeToolStatus = "Received data from ${event.name}")
}
}
else -> {}
}
}
// Finalize message once stream completes
_uiState.update {
it.copy(
messages = it.messages + it.currentResponse,
currentResponse = "",
activeToolStatus = null,
isLoading = false
)
}
}
}
override fun onCleared() {
super.onCleared()
// Cleanly evicts conversation memory from RAM when ViewModel is destroyed
session.close()
}
}
جاریسازی مستقیم کدهای نوشتاری
برای میاناهای مکالمهای که جزئیات اجرای ابزار نیازی به نمایش جداگانه در میانای کاربر ندارند، از تابع افزونه textTokens() در میانای Flow<AgentEvent> استفاده کنید:
import com.google.mlkit.genai.agent.textTokens
import com.google.mlkit.genai.prompt.Content
val content = Content.builder().text("What is my order status?").build()
session.runAsync(content)
.textTokens()
.collect { token ->
print(token)
}
اجرای تکنوبتی
برای کارهای یکباره که سابقه مکالمه نباید در RAM حفظ شود (برای مثال، برچسبگذاری عکس یا خلاصهسازی نوشتار زودگذر)، مستقیماً متد runner.runSingleTurnAsync(content) را فراخوانی کنید:
// Ephemeral single-turn: cleans up session state from RAM automatically upon completion
val content = Content.builder()
.text("Summarize the order history for customer #1042: ...")
.build()
runner.runSingleTurnAsync(content)
.textTokens()
.collect { token ->
displaySummary(token)
}
هنگام فراخوانی مستقیم روش runner.runSingleTurnAsync(content)، نمونه AgentRunner بهطور داخلی یک شناسه جلسه زودگذر اختصاص میدهد و پساز تکمیل، بهطور خودکار وضعیت مکالمه را از حافظه حذف میکند تا از نشت حافظه جلوگیری شود.
چرخه حیات جلسه و مدیریت حافظه
سابقه مکالمه چندنوبتی در حافظه ذخیره میشود و با مقدار sessionId
نمایهگذاری میشود:
- تغییرات پیکربندی: حفظ نمونه
AgentSessionدر کلاس AndroidViewModelبهطور خودکار سابقه مکالمه را در چرخشهای صفحه حفظ میکند، بدون اینکه نیاز به لولهکشی رشتهها در هر چرخش باشد. - اخراج جلسه: متد
session.delete()را فراخوانی کنید تا وقتی رشته مکالمهای رد میشود، جلسه را بهطور همزمان اخراج کنید، یا متد غیرتعلیقیsession.close()را در متدViewModel.onCleared()فراخوانی کنید تا RAM آزاد شود. - اخراج انبوه: برای آزاد کردن یک جلسه خاص، روش
runner.deleteSession(sessionId)را فراخوانی کنید، یا برای اخراج کردن همه مکالمههای ذخیرهشده در حافظه نهان، روشrunner.clearAllSessions()را فراخوانی کنید. - چرخه حیات Runner: کلاس
AgentRunnerمیانایAutoCloseableرا پیادهسازی میکند. هنگام همرسانی اجراکننده در صفحهها، دستههایAgentSessionجداگانه را در روشViewModel.onCleared()هر صفحه ببندید، و هنگام خاموش کردن کامل اجراکننده، روشrunner.close()را فراخوانی کنید. - جلسههای همزمان: یک نمونه
AgentRunnerمیتواند بهطور همزمان میزبان چندین جلسه متمایز باشد. هر مقدارsessionIdسابقه مکالمه مجزایی را حفظ میکند.
خطا در انجام درخواست با کلاس GenAiException
خطاهای زمان اجرا درطول اجرای عامل، مثلاً وقتی عامل به حداکثر
نوبتهای ابزار بدون تولید پاسخ میرسد یا وقتی هوش مصنوعی دروندستگاهی موقتاً
دردسترس نیست، بهعنوان استثنای GenAiException
(com.google.mlkit.genai.common.GenAiException) صادر یا ایجاد میشوند. خطاها را بهصورت واکنشی
بااستفاده از اپراتور Kotlin Coroutines Flow catch مدیریت کنید:
session.runAsync(content)
.catch { e ->
if (e is GenAiException) {
when (e.errorCode) {
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED -> {
// Turn limit exceeded
}
GenAiException.ErrorCode.NOT_AVAILABLE -> {
// Gemini Nano or AICore unavailable
}
GenAiException.ErrorCode.BUSY -> {
// System busy or rate limit reached
}
else -> {
// Other GenAiException errors
}
}
}
}
.collect { event ->
// Process response events
}
کدهای خطای کلیدی
-
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): عامل از حداکثر حد نوبت (حدmaxTurns) فراتر رفت بدون اینکه پاسخ نهایی تولید کند. GenAiException.ErrorCode.NOT_AVAILABLE(8): مدل Gemini Nano یا سرویس سیستم AICore دردسترس یا آماده نیست.-
GenAiException.ErrorCode.BUSY(9): سیستم مشغول است یا برنامه برقراری تماس از سهمیهاش فراتر رفته است.