
В этом руководстве рассказывается, как реализовать на устройстве семантический поиск – метод поиска, который находит релевантный контент на основе значения запроса, а не только по ключевым словам. Семантический поиск избавляет от необходимости вручную создавать и поддерживать таблицы синонимов или таксономии тегов. Чтобы реализовать семантический поиск на устройстве, используйте следующие библиотеки:
- ML Kit SDK, в частности Embeddings 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 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(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.
Рекомендуем начать переиндексацию эмбеддингов, как только используемая вами модель будет объявлена устаревшей. Чтобы узнать, когда прекратится поддержка модели, используйте 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) входных данных, предоставленных вызывающим абонентом, а не генерирует новый текст или медиаконтент, числовые векторы по своей сути не представляют рисков для безопасности генерируемого контента. Отказ от фильтрации на устройстве также позволяет сохранить низкую задержку при индексировании и поиске, поскольку ЦП не будет перегружен во время индексирования документов с высокой пропускной способностью и обработки запросов в реальном времени. Чтобы повысить безопасность, следуйте этим рекомендациям:- Модерируйте ненадежные входные данные и индексируемый контент. Если ваше приложение индексирует или ищет ненадежный пользовательский контент или внешние данные, применяйте модерацию контента и фильтры правил к исходному тексту или изображениям, прежде чем индексировать их или показывать результаты поиска.
- Защитите конвейеры генеративного ИИ. Если извлеченные документы или фрагменты передаются в качестве контекста в модель генеративного ИИ, убедитесь, что в ней и в ее выходных данных, предназначенных для пользователей, включены фильтры безопасности и правила в отношении контента.