
يشرح هذا الدليل كيفية تنفيذ ميزة البحث الدلالي على الجهاز فقط، وهي تقنية بحث تعثر على المحتوى ذي الصلة استنادًا إلى معنى طلب البحث، وليس فقط من خلال الكلمات الرئيسية. يغني البحث الدلالي عن الحاجة إلى إنشاء جداول مرادفات مخصّصة أو تصنيفات علامات تتبّع وصيانتها يدويًا. لتنفيذ البحث الدلالي على الجهاز فقط، استخدِم المكتبات التالية:
- حزمة تطوير البرامج (SDK) الخاصة بـ "حزمة تعلّم الآلة"، وتحديدًا واجهة Embeddings API، لإنشاء تضمينات المتّجهات
- AndroidX AppSearch لتخزين تضمينات المتّجهات والبحث في البيانات المنظَّمة باستخدام مقاييس المسافة بين جيب التمام للعثور على المحتوى حسب دلالاته
للاطّلاع على مثال عملي، يمكنك مراجعة نموذج تطبيق حزمة تعلّم الآلة للذكاء الاصطناعي التوليدي على GitHub.
المتطلبات الأساسية
لاستخدام ميزة "البحث الدلالي على الجهاز فقط"، يجب استيفاء المتطلبات التالية المتعلقة بالجهاز والحساب:
- يجب أن يكون جهازك متوافقًا مع Android AICore. تستخدم معظم الأجهزة المتوافقة مع AICore نماذج مسرَّعة بواسطة وحدة المعالجة العصبية (NPU)، بينما تعود الأجهزة المتوافقة المتبقية إلى نموذج وحدة المعالجة المركزية (CPU) (مع تسريع XNNPACK).
- حساب Google على جهازك هو جزء من برنامج "معاينة المطوّر لخدمة AICore".
- يحتوي جهازك على أحدث إصدار من حزمة APK لمعاينة المطوّرين في AICore: على جهازك، افتح صفحة Android AICore على "متجر Google Play" وانقر على تحديث لتثبيت أحدث إصدار من حزمة APK. بدلاً من ذلك، انتقِل إلى الإعدادات على جهازك، وابحث عن خدمات النظام أو خدمات Google، وانقر على Android AICore (الإصدار التجريبي)، ثم انقر على تحديث في حال توفّر تحديث.
- تثبيت أحدث إصدار من تطبيق "خدمات الحوسبة الخاصة" على جهازك: على جهازك، افتح صفحة "خدمات الحوسبة الخاصة" في "متجر Google Play" وانقر على تحديث لتثبيت أحدث إصدار من التطبيق. أو انتقِل إلى متجر Google Play > إدارة التطبيقات والأجهزة > إدارة > خدمات الحوسبة الخاصة وانقر على تحديث في حال توفّر تحديث.
النماذج والإعدادات المتوافقة
تستخدم واجهة برمجة التطبيقات ML Kit Embeddings API مجموعة نماذج EmbeddingGemma من Google، والتي تتم إدارتها من خلال
Android AICore. على وجه التحديد، تستخدم Embeddings API نموذج
EmbeddingGemma 2
(embedding_gemma_v2) لإنشاء تضمينات لإدخالات النصوص والصور.
في ما يلي المواصفات والحدود القصوى للإدخال:
| سمة الإدخال | EmbeddingGemma 2 | ملاحظات الاستخدام |
|---|---|---|
| الوسائط المتوافقة | النص والصورة (Modality.TEXT وModality.IMAGE) |
للتأكّد من أنّ إدخال الصور متاح، تحقَّق من أنّ model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) يعرض true في وقت التشغيل. |
| الحدّ الأقصى لعدد الرموز المميّزة | 256 رمزًا مميزًا | يمكنك الفحص باستخدام طريقة model.getTokenLimit(). يتم ترميز كل صورة إلى 140 رمزًا مميزًا ناعمًا، ما يترك 116 رمزًا مميزًا للنص المصاحب. |
| درجة دقة الصورة | ما يصل إلى 768 بكسل على الجانب الأقصر | تقبل الدالة الإنشائية للفئة ImagePart أنواع الوسيطات Bitmap أو Uri أو ByteArray. يتم تلقائيًا تصغير الصور التي يقلّ بُعدها الأقصر عن 768 بكسل من خلال حزمة تطوير البرامج (SDK) مع الحفاظ على نسبة العرض إلى الارتفاع. لتحقيق الأداء الأمثل واستخدام الذاكرة بشكل أفضل، تجنَّب نسب العرض إلى الارتفاع القصوى. |
يبلغ بُعد متّجه الناتج 768 (عنصر FloatArray من الأعداد العشرية ذات 32 بت). يمكنك فحص بُعد متجه الناتج في وقت التشغيل باستخدام طريقة model.getMetadata()?.dimension.
البدء
لضبط مشروعك لاستخدام البحث الدلالي، أضِف التبعيات التالية إلى ملف build.gradle.kts (أو build.gradle) على مستوى الوحدة.
Kotlin
dependencies {
// ...
implementation("com.google.mlkit:genai-embedding:1.0.0-alpha1")
implementation("androidx.appsearch:appsearch:1.2.0-alpha01")
implementation("androidx.appsearch:appsearch-local-storage:1.2.0-alpha01")
implementation("androidx.concurrent:concurrent-futures-ktx:1.3.0")
// ...
}
Groovy
dependencies {
// ...
implementation 'com.google.mlkit:genai-embedding:1.0.0-alpha1'
implementation 'androidx.appsearch:appsearch:1.2.0-alpha01'
implementation 'androidx.appsearch:appsearch-local-storage:1.2.0-alpha01'
implementation 'androidx.concurrent:concurrent-futures-ktx:1.3.0'
// ...
}
الحصول على نموذج تضمين
استخدِم
EmbeddingGeneration.getEmbeddingModels()
لإدراج نماذج التضمين المتاحة على الجهاز، واختَر أحدها من خلال
توقيع النموذج. أغلِق أي نماذج لا تستخدمها.
// Prefer EmbeddingGemma 2 (text and image), then EmbeddingGemma 1 (text only).
val preferredSignatures = listOf("embedding_gemma_v2", "embedding_gemma_v1")
suspend fun selectEmbeddingModel(): EmbeddingModel? {
val models = EmbeddingGeneration.getEmbeddingModels()
val selected = preferredSignatures.firstNotNullOfOrNull { signature ->
models.firstOrNull { signature in it.getModelSignature() }
} ?: models.firstOrNull()
models.filter { it !== selected }.forEach { it.close() }
return selected
}
للحصول على نموذج معيّن، أدخِل توقيعه:
EmbeddingGeneration.getEmbeddingModels("embedding_gemma_v2").
قبل إنشاء التضمينات، نزِّل النموذج إذا لزم الأمر وجهِّزه:
suspend fun prepareModel(model: EmbeddingModel): Boolean {
when (model.checkStatus()) {
FeatureStatus.UNAVAILABLE -> return false
FeatureStatus.DOWNLOADABLE, FeatureStatus.DOWNLOADING -> model.download().collect()
}
model.warmup()
return true
}
يمكنك استدعاء model.close() عندما لا تعود بحاجة إلى النموذج.
تحديد مخطط AppSearch
لتفعيل البحث عن التشابه الدلالي، يجب تحديد مخطط AppSearch حيث يتم تكوين خاصية المتجه بشكل صريح لفهرسة التشابه.
val documentSchema = AppSearchSchema.Builder("my_schema_type")
.addProperty(
AppSearchSchema.StringPropertyConfig.Builder("text")
.setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build()
)
.addProperty(AppSearchSchema.StringPropertyConfig.Builder("imageUri").build())
.addProperty(
AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding")
.setIndexingType(AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_SIMILARITY)
.setQuantizationType(AppSearchSchema.EmbeddingPropertyConfig.QUANTIZATION_TYPE_8_BIT)
.build()
)
.build()
فهرسة محتوى المستند
لفهرسة محتوى المستند، يجب إنشاء تضمين متّجه لكل عنصر Content، وتضمينه في مثيل GenericDocument، وحفظه في قاعدة بيانات AppSearch.
عند إنشاء عنصر EmbeddingVector، يجب أيضًا تمرير قيمة modelSignature النشطة. تستخدم AppSearch سلسلة modelSignature هذه لمنع
طلبات البحث غير المتوافقة.
suspend fun syncDatabase(
model: EmbeddingModel,
session: AppSearchSession,
documents: List<Pair<String, Uri?>>,
) {
val modelSignature = model.getModelSignature()
val isImageSupported =
model.getMetadata()?.supportedModalities?.contains(Modality.IMAGE) == true
val docsWithEmbedding = mutableListOf<GenericDocument>()
for ((index, document) in documents.withIndex()) {
val (text, imageUri) = document
// Generate the embedding from the text and/or image.
val imagePart = if (imageUri != null && isImageSupported) ImagePart(imageUri) else null
val vector = try {
val parts = listOfNotNull(if (text.isNotBlank()) TextPart(text) else null, imagePart)
if (parts.isEmpty()) continue
model.generateEmbedding(EmbeddingRequest(Content(parts))).embedding.vector
} finally {
imagePart?.close()
}
// Associate the vector with the signature of the model that generated it.
val docBuilder = GenericDocument.Builder<GenericDocument.Builder<*>>(
"my_namespace",
"doc_$index",
"my_schema_type"
)
.setPropertyString("text", text)
.setPropertyEmbedding("embedding", EmbeddingVector(vector, modelSignature))
if (imageUri != null) {
docBuilder.setPropertyString("imageUri", imageUri.toString())
}
docsWithEmbedding.add(docBuilder.build())
}
// Clear the old database by force-overriding the schema, then set the schema again.
session.setSchemaAsync(SetSchemaRequest.Builder().setForceOverride(true).build()).await()
session.setSchemaAsync(SetSchemaRequest.Builder().addSchemas(documentSchema).build()).await()
val putRequest = PutDocumentsRequest.Builder().addGenericDocuments(docsWithEmbedding).build()
session.putAsync(putRequest).await()
}
تنفيذ البحث الدلالي
لإجراء طلب بحث دلالي، عليك تحويل طلب البحث (النص و/أو الصورة) إلى متّجه تضمين، وضبط مَعلمات البحث عن المتّجهات، وإرسال طلب البحث إلى قاعدة بيانات AppSearch.
اقتراحات:
- تستخدم AppSearch مقياس جيب التمام
(
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE) تلقائيًا، لذا لا تحتاج إلى ضبطه. - يتم ترتيب النتائج حسب صيغة
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - استخدِم السلسلة المنطقية
"semanticSearch(getEmbeddingParameter(0), $threshold)"لفلترة النتائج التي يتطابق فيها التشابه مع قيمة الحدّ الأدنى (عادةً 0.5 أو أعلى).
data class Match(val text: String, val imageUri: Uri?, val score: Double)
suspend fun search(
model: EmbeddingModel,
session: AppSearchSession,
queryText: String,
imageUri: Uri? = null,
threshold: Double = 0.5,
): List<Match> {
val modelSignature = model.getModelSignature()
val isImageSupported =
model.getMetadata()?.supportedModalities?.contains(Modality.IMAGE) == true
// Generate the query embedding from the text and/or image.
val imagePart = if (imageUri != null && isImageSupported) ImagePart(imageUri) else null
val queryVector = try {
val parts = listOfNotNull(if (queryText.isNotBlank()) TextPart(queryText) else null, imagePart)
if (parts.isEmpty()) return emptyList()
model.generateEmbedding(EmbeddingRequest(Content(parts))).embedding.vector
} finally {
imagePart?.close()
}
val searchSpec = SearchSpec.Builder()
.addFilterSchemas("my_schema_type")
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
.setResultCountPerPage(5)
// Add the query vector as parameter 0.
.addEmbeddingParameters(EmbeddingVector(queryVector, modelSignature))
.build()
val queryExpression = "semanticSearch(getEmbeddingParameter(0), $threshold)"
val results = session.search(queryExpression, searchSpec)
return try {
results.nextPageAsync.await().map {
Match(
text = it.genericDocument.getPropertyString("text") ?: "",
imageUri = it.genericDocument.getPropertyString("imageUri")?.let(Uri::parse),
// rankingSignal contains the computed similarity score.
score = it.rankingSignal,
)
}
} finally {
results.close()
}
}
استخدام فهرس بحث مختلط
لتقديم أفضل تجربة للمستخدم، ننصح باستخدام استراتيجية مختلطة تتضمّن البحث الدلالي والبحث بالكلمات الرئيسية. عندما يكتب المستخدم بنشاط في شريط البحث، استخدِم البحث بالكلمات الرئيسية لعرض اقتراحات فورية أثناء الكتابة، وعندما يُدخل طلب البحث المكتمل، أو حتى عند فواصل الكلمات، استخدِم البحث الدلالي لقراءة وفهم وجلب أفضل التطابقات المفاهيمية.
إعادة فهرسة قاعدة بيانات المتّجهات
توجد التضمينات التي تنشئها إصدارات النماذج المختلفة في مساحات متّجهات غير متوافقة ولا يمكن مقارنتها مباشرةً. يجب إعادة فهرسة قاعدة بيانات AppSearch عند تعديل نموذج التضمين.
ننصحك ببدء إعادة فهرسة التضمينات فور دخول النموذج الذي تستخدمه في فترة الإيقاف النهائي. يمكنك استخدام Embeddings API للاطّلاع على الجدول الزمني لإيقاف النموذج نهائيًا. استدعِ طريقة model.getMetadata() لاسترداد عنصر EmbeddingMetadata والتحقّق من خصائص تاريخ دورة الحياة هذه (التي يتم عرضها كعناصر LocalDate بالتوقيت العالمي المنسق):
- تشير القيمة
deprecationDateإلى بداية فترة إيقاف النموذج نهائيًا. يجب البدء بإعادة الفهرسة بينما لا يزال النموذج يعمل، وإكمالها قبل إيقافه رسميًا. - تشير قيمة
shutdownDateإلى وقت إيقاف النموذج نهائيًا وعدم إتاحته بعد ذلك. وبعد هذا التاريخ، لن يكون النموذج متاحًا للاستنتاج، وستتم إعادة فهرسة التضمينات الحالية لهذا النموذج باستخدام أحدث نموذج متاح.
ونُعلمك أيضًا بالنماذج التي سيتم إيقافها نهائيًا، والمخططات الزمنية لنقل البيانات، وتواريخ الإيقاف المستهدفة من خلال ملاحظات إصدار "حزمة تعلّم الآلة" وتعديلات مستندات المطوّرين.
أفضل الممارسات
لضمان تجربة مستخدم جيدة، اتّبِع الممارسات التالية عند دمج تضمينات المتّجهات في تطبيق Android:
- جدولة أعباء عمل الفهرسة: يجب دائمًا جدولة أعباء عمل الفهرسة في الخلفية باستخدام مكتبة WorkManager. اضبط المهام التي يتم تنفيذها في الخلفية لتعمل فقط عندما يكون الجهاز قيد الشحن وغير مستخدَم من قِبل أي برنامج حاليًا. تفرض حزمة تعلّم الآلة حدودًا على حصة استخدام النماذج، على غرار واجهات برمجة التطبيقات الخاصة بالنماذج اللغوية الكبيرة، مع حدود أكثر مرونة عندما يكون الجهاز قيد الشحن. في وضع "معاينة المطوّر"، يمكنك تجاوز حدود الحصة لأغراض الاختبار.
- تحسين مساحة التخزين وطلبات البحث: يمكنك تحسين حجم قاعدة البيانات ووقت استجابة طلبات البحث باستخدام ميزتَي "التقدير التقريبي" و"اقتطاع الأبعاد":
- اضبط مخطط
AppSearchSchemaلاستخدام الثابتQUANTIZATION_TYPE_8_BIT، كما هو موضّح في تحديد مخطط AppSearch. يؤدي ذلك إلى تقليل استخدام الذاكرة مع الحدّ من التأثير في الجودة من خلال تقليص دقة كل إحداثي إلى عدد صحيح مكوّن من 8 بت. - استخدِم تقنية Matryoshka Representation Learning (MRL) التي يتيحها نموذج EmbeddingGemma 2 لاقتطاع متّجه التضمين. لمزيد من المعلومات، يُرجى الاطّلاع على EmbeddingGemma 2 على HuggingFace.
- اضبط مخطط
- اتساق التوقيع: لا تستعلم عن مساحة متجه باستخدام توقيع غير متطابق. في حال ترقية إصدار نموذج التضمين، يجب محو قاعدة بيانات AppSearch وإعادة إنشاء التضمينات لجميع المستندات باستخدام توقيع النموذج الجديد.
- الأمان والإشراف على المحتوى: أنت مسؤول عن تنفيذ الإشراف على المحتوى وفلترة الأمان في تطبيقك. على عكس واجهات برمجة التطبيقات الخاصة بالنصوص أو الصور من إنشاء الذكاء الاصطناعي التوليدي، لا تتضمّن واجهة Embeddings API في حزمة تعلّم الآلة فلاتر أمان مدمجة. بما أنّ النموذج يُخرج تمثيلات متجهات رقمية (إحداثيات
FloatArray) للإدخال الذي يقدّمه المتصل بدلاً من إنشاء نص أو وسائط جديدة، لا تشكّل المتجهات الرقمية بطبيعتها مخاطر على أمان المحتوى التوليدي. يؤدي حذف فلترة الأمان على الجهاز فقط أيضًا إلى الحفاظ على أداء الفهرسة والبحث بزمن انتقال منخفض من خلال تجنُّب تنافس وحدة المعالجة المركزية (CPU) أثناء فهرسة المستندات بمعدّل نقل بيانات مرتفع وطلبات البحث في الوقت الفعلي. لتحسين مستوى الأمان، اتّبِع الإرشادات التالية:- الإشراف على المدخلات غير الموثوق بها والمحتوى المفهرَس: إذا كان تطبيقك يفهرس أو يبحث في محتوى غير موثوق به من إنشاء المستخدمين أو بيانات خارجية، يجب تطبيق فلاتر الإشراف على المحتوى والسياسات على النصوص أو الصور المصدر قبل فهرستها أو عرض نتائج البحث.
- حماية مسارات الذكاء الاصطناعي التوليدي اللاحقة: إذا تم تمرير المستندات أو الفقرات التي تم استردادها كسياق إلى نموذج ذكاء اصطناعي توليدي لاحق، يجب التأكّد من تفعيل فلاتر الأمان وسياسات المحتوى في هذا النموذج اللاحق ومخرجاته الموجّهة للمستخدمين.