من خلال مهام سير العمل المستندة إلى الذكاء الاصطناعي الوكيل على الجهاز فقط في حزمة تعلّم الآلة (com.google.mlkit:genai-agent،
المتاحة من خلال برنامج معاينة المطوّر في AICore)، يمكنك إنشاء
وكلاء محادثات مستقلين ومتعدّدي الجولات مستندين إلى Gemini Nano وخدمة نظام
Android AICore.
تستند فئة AgentRunner إلى فئة GenerativeModel التي توفّر
إمكانية الوصول المباشر إلى النموذج، وتعمل على أتمتة
الحلقات الوكيلة الكاملة: تخطيط الإجراءات المتعدّدة الخطوات، واستدعاء الأدوات المكتوبة (مثل
الاستعلام عن مستودع التطبيقات أو استدعاء واجهة برمجة تطبيقات خلفية أو قراءة قاعدة بيانات محلية)،
وتقييم نتائج الأدوات، وإدارة سياق المحادثة محليًا على الجهاز
بدون أي وقت استجابة للشبكة وبخصوصية كاملة للمستخدم. While the GenerativeModel class
supports client-managed multi-turn history using a List<Content> object (with
USER and MODEL roles) and a sessionId value, the AgentRunner class
provides managed multi-turn sessions (using the AgentSession interface) that
record in-memory conversation history automatically across turns so that you
don't have to manage transcripts manually. يتم أيضًا تخزين تعريفات الأدوات الثابتة وتعليمات النظام تلقائيًا في الذاكرة المؤقتة على الجهاز فقط، ما يقلل من وقت الاستجابة لأول رمز مميز بدون الحاجة إلى ضبط إعدادات إضافية.
المكوّنات الأساسية
- الفئة
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: هي مسارFlowتفاعلي في Kotlin يُصدر رموزًا مميزة للنصوص وتعديلات على الأدوات. - الفئة
AgentTool: دوال مكتوبة يمكن للنموذج استدعاؤها، ويتم تحديدها باستخدام AppFunctions في Android (@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المتطلبات الأساسية والتبعيات
تأكَّد من أنّ بيئة التطوير وإعدادات Gradle تستوفي المتطلبات التالية قبل دمج Agent API.
متطلبات النظام الأساسي والأجهزة
- إصدار Android: الإصدار 14 من نظام التشغيل Android (المستوى 34 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث المسجَّلة في برنامج معاينة المطوّر لخدمة AICore.
- الأجهزة المتوافقة: للاطّلاع على قائمة بالأجهزة المتوافقة وإصدارات نموذج Gemini Nano الخاصة بها، يُرجى الاطّلاع على الأجهزة المتوافقة مع ميزة إنشاء الردود المخصّصة والوكيل.
- الحدّ الأدنى لحزمة تطوير البرامج (SDK):
minSdk 26أو إصدار أحدث - Kotlin: الإصدار 2.3 من 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 يتطلّب المكوّن الإضافي appfunctions-compiler KSP.
البدء السريع: إعداد وكيل
توضّح عيّنة التعليمات البرمجية التالية كيفية إنشاء مثيل لوكيل ومُشغِّل على الجهاز فقط:
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)، يمكن استدعاء AppFunctions من خلال المساعدين على الجهاز فقط المعتمدين من النظام الأساسي والذين لديهم إذن النظام EXECUTE_APP_FUNCTIONS. إذا كانت الأداة مخصّصة حصريًا لوكيل تطبيقك الداخلي (على سبيل المثال، إدارة سلال تسوّق المستخدمين أو سجلات قواعد البيانات الخاصة)، يمكنك تقييد وصول المتصل باستخدام الثابت 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(درجة الحرارة) إلى دالة الإنشاءAgentRunner. تجنَّب ضبط درجات حرارة على النمط السحابي (> 0.4f)، لأنّ ذلك يزيد من مخاطر الهلوسة وتدهور المخطط على النماذج المخصّصة للأجهزة الجوّالة.
التنفيذ المتعدّد الأدوار وأحداث البث
احصل على مثيل AgentSession باستخدام طريقة runner.getOrCreateSession(sessionId) داخل فئة ViewModel في Android. تعرض الجلسة سلسلة بيانات 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داخل فئةViewModelفي Android إلى الحفاظ على سجلّ المحادثات تلقائيًا عند تدوير الشاشة بدون الحاجة إلى ربط السلاسل في كل مرة. - إخلاء الجلسة: يمكنك استدعاء الطريقة
session.delete()لإخلاء جلسة بشكل متزامن عند إغلاق سلسلة محادثات، أو استدعاء الطريقةsession.close()غير المعلّقة في الطريقةViewModel.onCleared()لإخلاء ذاكرة RAM. - الإخلاء المجمّع: يمكنك استدعاء الطريقة
runner.deleteSession(sessionId)لإخلاء جلسة معيّنة، أو الطريقةrunner.clearAllSessions()لإخلاء جميع المحادثات المخزّنة مؤقتًا. - مراحل نشاط المشغِّل: تنفّذ الفئة
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): النظام مشغول أو تجاوز تطبيق الاتصال الحصة المخصّصة له.