В этом руководстве рассказывается, как реализовать на устройстве семантический поиск – метод поиска, который находит релевантный контент на основе значения запроса, а не только по ключевым словам. Семантический поиск избавляет от необходимости вручную создавать и поддерживать таблицы синонимов или таксономии тегов. Чтобы реализовать семантический поиск на устройстве, используйте следующие библиотеки:
- ML Kit SDK, в частности Embedding API, для создания векторных эмбеддингов.
- AndroidX AppSearch для хранения векторных эмбеддингов и поиска структурированных данных с использованием метрик косинусного расстояния, чтобы находить контент по его семантике.
Требования
Чтобы использовать семантический поиск на устройстве, должны выполняться следующие требования к устройству и аккаунту:
- Ваше устройство поддерживает Android AICore. Большинство устройств с поддержкой AICore используют модели с ускорением NPU, а остальные поддерживаемые устройства возвращаются к модели ЦП (с ускорением XNNPACK).
- Аккаунт Google на вашем устройстве участвует в программе предварительной версии для разработчиков AICore.
- На устройстве установлена последняя предварительная версия APK-файла AICore для разработчиков. Чтобы установить ее, откройте страницу Android AICore в Google Play и нажмите Обновить. Вы также можете открыть Настройки устройства, найти Системные службы или Сервисы Google, нажать Android AICore (бета) и выбрать Обновить, если доступно обновление.
- На устройстве установлена последняя версия приложения сервисы конфиденциальных вычислений. Чтобы установить ее, откройте страницу сервисы конфиденциальных вычислений в Google Play и нажмите Обновить. Вы также можете перейти в Google Play > Управление приложениями и устройством > Управление > сервисы конфиденциальных вычислений и нажать Обновить, если доступно обновление.
Поддерживаемые модели и конфигурация
ML Kit Embedding API использует семейство моделей EmbeddingGemma от Google, управляемое Android AICore. В частности, Embedding API использует EmbeddingGemma 2 (embedding_gemma_v2) для генерации встраиваний для текстовых и графических входных данных.
Ниже приведены спецификации и ограничения для входных данных.
| Свойство ввода | EmbeddingGemma 2 | Примечания |
|---|---|---|
| Поддерживаемые модальности | Текст и изображение (Modality.TEXT, Modality.IMAGE) |
Чтобы убедиться, что ввод изображений поддерживается, проверьте во время выполнения, что model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) возвращает true. |
| Лимит токенов | 256 токенов | Проверьте с помощью метода model.getTokenLimit(). Каждое изображение кодируется в 70 токенов, оставляя 186 токенов для сопровождающего текста. |
| Разрешение изображения | До 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(EmbeddingOptions()).
Как определить схему AppSearch
Чтобы включить поиск по семантическому сходству, необходимо определить схему AppSearch, в которой свойство вектора явно настроено для индексирования сходства.
// Define the document schema
val documentSchema = AppSearchSchema.Builder("my_schema_type")
.addProperty(
AppSearchSchema.StringPropertyConfig.Builder("text")
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
.build()
)
.addProperty(
AppSearchSchema.StringPropertyConfig.Builder("imageUri")
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE)
.setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE)
.build()
)
.addProperty(
AppSearchSchema.EmbeddingPropertyConfig.Builder("embedding")
.setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
.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(documents: List<Pair<String, Uri?>>) {
val modelSignature = model?.getModelSignature() ?: return
val docsWithEmbedding = mutableListOf<GenericDocument>()
for ((index, item) in documents.withIndex()) {
val (content, imageUri) = item
// 1. Generate embedding (text and/or image)
val parts = mutableListOf<Part>()
if (content.isNotBlank()) {
parts.add(TextPart(content))
}
var imagePart: ImagePart? = null
val vector = try {
if (imageUri != null && isImageSupported) {
imagePart = ImagePart(imageUri)
parts.add(imagePart)
}
if (parts.isEmpty()) continue
val request = EmbeddingRequest(
Content(parts)
)
model?.generateEmbedding(request)?.embedding?.vector
?: continue
} finally {
imagePart?.close()
}
// 2. Build the AppSearch document
val docBuilder = GenericDocument.Builder<GenericDocument.Builder<*>>(
"my_namespace",
"doc_$index",
"my_schema_type"
)
.setPropertyString("text", content)
// Associate the vector with the signature that generated it
.setPropertyEmbedding("embedding", EmbeddingVector(vector, modelSignature))
if (imageUri != null) {
docBuilder.setPropertyString("imageUri", imageUri.toString())
}
docsWithEmbedding.add(docBuilder.build())
}
// 3. Clear the old database by force-overriding the schema
val clearRequest = SetSchemaRequest.Builder().setForceOverride(true).build()
session?.setSchemaAsync(clearRequest)?.await()
val schemaRequest = SetSchemaRequest.Builder().addSchemas(documentSchema).build()
session?.setSchemaAsync(schemaRequest)?.await()
// 4. Index documents
val putRequest = PutDocumentsRequest.Builder().addGenericDocuments(docsWithEmbedding).build()
session?.putAsync(putRequest)?.await()
}
Выполнить семантический поиск
Чтобы выполнить семантический запрос, преобразуйте поисковый запрос (текст и/или изображение) в вектор встраивания, настройте параметры векторного поиска и отправьте запрос в базу данных AppSearch.
Рекомендации
- Задайте для показателя по умолчанию константу
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE. - Ранжируйте результаты по формуле
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - Используйте логическую строку
"semanticSearch(getEmbeddingParameter(0), $threshold)", чтобы отфильтровать результаты, в которых сходство соответствует пороговому значению (обычно 0,5 или выше).
suspend fun search(
queryText: String,
imageUri: Uri? = null,
threshold: Double = 0.5
): List<Triple<String, Uri?, Double>> {
val modelSignature = model?.getModelSignature() ?: return emptyList()
// 1. Generate query embedding (text and/or image)
val parts = mutableListOf<Part>()
if (queryText.isNotBlank()) {
parts.add(TextPart(queryText))
}
var imagePart: ImagePart? = null
val queryVector = try {
if (imageUri != null && isImageSupported) {
imagePart = ImagePart(imageUri)
parts.add(imagePart)
}
if (parts.isEmpty()) return emptyList()
val request = EmbeddingRequest(
content = Content(parts)
)
model?.generateEmbedding(request)?.embedding?.vector
?: return emptyList()
} finally {
imagePart?.close()
}
// 2. Configure search specifications
val searchSpec = SearchSpec.Builder()
.addFilterSchemas("my_schema_type")
.setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true)
.setResultCountPerPage(5)
// Add the query vector as parameter 0
.addEmbeddingParameters(EmbeddingVector(queryVector, modelSignature))
.build()
// 3. Execute search
val queryExpression = "semanticSearch(getEmbeddingParameter(0), $threshold)"
val results = session?.search(queryExpression, searchSpec) ?: return emptyList()
return try {
val page = results.nextPageAsync.await()
page.map {
val text = it.genericDocument.getPropertyString("text") ?: ""
val matchedImageUri = it.genericDocument.getPropertyString("imageUri")?.let(Uri::parse)
// rankingSignal contains the computed similarity score
Triple(text, matchedImageUri, it.rankingSignal)
}
} finally {
results.close()
}
}
Используйте гибридный индекс поиска
Для удобства пользователей мы рекомендуем использовать гибридную стратегию, включающую как семантический поиск, так и поиск по ключевым словам. Когда пользователь вводит запрос в строку поиска, используйте поиск по ключевым словам, чтобы показывать мгновенные подсказки. Когда пользователь вводит запрос полностью или даже делает паузу между словами, используйте семантический поиск, чтобы прочитать, понять и получить лучшие концептуальные совпадения.
Переиндексируйте векторную базу данных
Эмбеддинги, созданные разными версиями модели, находятся в несовместимых векторных пространствах, и их нельзя сравнивать напрямую. При каждом обновлении модели эмбеддинга необходимо повторно индексировать базу данных AppSearch.
Рекомендуем начать переиндексацию эмбеддингов, как только используемая вами модель будет объявлена устаревшей. Чтобы проверить график прекращения поддержки модели, используйте Embedding 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 Embedding API не включает встроенные фильтры безопасности. Поскольку модель выводит числовые векторные представления (координаты
FloatArray) входных данных, предоставленных вызывающим абонентом, а не генерирует новый текст или медиаконтент, числовые векторы по своей сути не представляют рисков для безопасности генерируемого контента. Отказ от фильтрации на устройстве также позволяет сохранить низкую задержку при индексировании и поиске, поскольку ЦП не будет перегружен во время индексирования документов с высокой пропускной способностью и обработки запросов в реальном времени. Чтобы повысить безопасность, следуйте этим рекомендациям:- Модерируйте ненадежные входные данные и индексируемый контент. Если ваше приложение индексирует или ищет ненадежный пользовательский контент или внешние данные, применяйте модерацию контента и фильтры правил к исходному тексту или изображениям, прежде чем индексировать их или показывать результаты поиска.
- Защитите конвейеры генеративного ИИ. Если извлеченные документы или фрагменты передаются в качестве контекста в модель генеративного ИИ, убедитесь, что в ней и в ее выходных данных, предназначенных для пользователей, включены фильтры безопасности и правила в отношении контента.