
В этом руководстве рассказывается, как реализовать на устройстве семантический поиск – метод поиска, который находит релевантный контент на основе значения запроса, а не только по ключевым словам. Семантический поиск избавляет от необходимости вручную создавать и поддерживать таблицы синонимов или таксономии тегов. Чтобы реализовать семантический поиск на устройстве, используйте следующие библиотеки:
- ML Kit SDK, в частности Embeddings API, для создания векторных эмбеддингов.
- AndroidX AppSearch для хранения векторных эмбеддингов и поиска структурированных данных с использованием метрик косинусного расстояния, чтобы находить контент по его семантике.
Рабочий пример можно найти в примере приложения ML Kit GenAI на GitHub.
Требования
Чтобы использовать семантический поиск на устройстве, должны выполняться следующие требования к устройству и аккаунту:
- Ваше устройство поддерживает Android AICore. Большинство устройств с поддержкой AICore используют модели с ускорением NPU, а остальные поддерживаемые устройства возвращаются к модели ЦП (с ускорением XNNPACK).
- Аккаунт Google на вашем устройстве участвует в программе предварительной версии для разработчиков AICore.
- На устройстве установлена последняя версия APK-файла предварительной версии AICore для разработчиков. Чтобы установить ее, откройте страницу Android AICore в Google Play и нажмите Обновить. Вы также можете открыть Настройки устройства, найти Системные службы или Сервисы Google, нажать Android AICore (бета) и выбрать Обновить, если доступно обновление.
- На устройстве установлена последняя версия приложения сервисы конфиденциальных вычислений. Чтобы установить ее, откройте страницу сервисы конфиденциальных вычислений в Google Play и нажмите Обновить. Вы также можете перейти в Google Play > Управление приложениями и устройством > Управление > сервисы конфиденциальных вычислений и нажать Обновить, если доступно обновление.
Поддерживаемые модели и конфигурация
ML Kit Embeddings API использует семейство моделей Google EmbeddingGemma, управляемое 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 в формате UTC):
- Значение
deprecationDateуказывает на начало периода прекращения поддержки модели. Рекомендуем начать переиндексирование, пока модель ещё работает, и завершить его до официального прекращения поддержки. shutdownDate– дата, когда модель будет выведена из эксплуатации и перестанет использоваться. После этой даты модель становится недоступной для инференса, а существующие эмбеддинги этой модели принудительно переиндексируются с использованием последней доступной модели.
Мы также сообщаем о предстоящем прекращении поддержки моделей, сроках миграции и целевых датах отключения в примечаниях к выпуску ML Kit и обновлениях документации для разработчиков.
Рекомендации
Чтобы обеспечить удобство для пользователей, следуйте этим рекомендациям при интеграции векторных вложений в приложение Android:
- Планируйте рабочие нагрузки индексирования. Всегда планируйте фоновые рабочие нагрузки индексирования с помощью библиотеки WorkManager. Настройте фоновые задачи так, чтобы они выполнялись, только когда устройство заряжается и не используется. ML Kit накладывает ограничения на использование моделей, аналогичные API LLM, с более либеральными ограничениями, когда устройство заряжается. В режиме предварительной версии для разработчиков вы можете обойти ограничения квоты для тестирования.
- Оптимизация хранилища и запросов. Оптимизируйте размер базы данных и задержку запросов с помощью квантования и усечения размерности:
- Настройте схему
AppSearchSchema, чтобы использовать константуQUANTIZATION_TYPE_8_BIT, как показано в разделе Как определить схему AppSearch. Это позволяет снизить использование памяти с минимальным влиянием на качество за счет уменьшения точности каждой координаты до 8-битного целого числа. - Используйте обучение представлениям Matryoshka (MRL), поддерживаемое моделью EmbeddingGemma 2, чтобы усечь вектор встраивания. Чтобы узнать больше, ознакомьтесь с информацией о модели EmbeddingGemma 2 на сайте Hugging Face.
- Настройте схему
- Согласованность сигнатур. Не запрашивайте векторное пространство, используя несоответствующую сигнатуру. Если вы обновите версию модели встраивания, вам нужно будет очистить базу данных AppSearch и заново сгенерировать встраивания для всех документов, используя новую сигнатуру модели.
- Безопасность и модерация контента. Вы несете ответственность за модерацию контента и фильтрацию в целях безопасности в своем приложении. В отличие от API для генерации текста или изображений, в ML Kit Embeddings API нет встроенных фильтров безопасности. Поскольку модель выводит числовые векторные представления (координаты
FloatArray) входных данных, предоставленных вызывающим абонентом, а не генерирует новый текст или медиаконтент, числовые векторы по своей сути не представляют рисков безопасности, связанных с генерируемым контентом. Отказ от фильтрации на устройстве также позволяет сохранить низкую задержку при индексировании и поиске, поскольку ЦП не будет перегружен во время индексирования документов с высокой пропускной способностью и обработки запросов в реальном времени. Чтобы повысить безопасность, следуйте этим рекомендациям:- Модерируйте ненадежные входные данные и индексируемый контент. Если ваше приложение индексирует или ищет ненадежный пользовательский контент или внешние данные, применяйте модерацию контента и фильтры правил к исходному тексту или изображениям, прежде чем индексировать их или показывать результаты поиска.
- Защитите конвейеры генеративного ИИ. Если извлеченные документы или фрагменты передаются в качестве контекста в модель генеративного ИИ, убедитесь, что в ней и в ее выходных данных включены фильтры безопасности и правила в отношении контента.