Este guia mostra como implementar a pesquisa semântica no dispositivo, uma técnica de pesquisa que encontra conteúdo relevante com base no significado da consulta, em vez de apenas por palavras-chave. A pesquisa semântica elimina a necessidade de criar e manter manualmente tabelas de sinônimos personalizadas ou taxonomias de marcação. Para implementar a pesquisa semântica no dispositivo, use as seguintes bibliotecas:
- SDK do Kit de ML, especificamente a API Embedding, para gerar embeddings de vetor.
- AppSearch do AndroidX para armazenar os embeddings de vetor e pesquisar dados estruturados usando métricas de distância de cosseno para encontrar conteúdo pela semântica.
Pré-requisitos
Para usar a pesquisa semântica no dispositivo, você precisa atender aos seguintes requisitos de dispositivo e conta:
- Seu dispositivo oferece suporte ao Android AICore. A maioria dos dispositivos compatíveis com o AICore usa modelos acelerados por NPU, enquanto os demais dispositivos compatíveis usam um modelo de CPU (com aceleração XNNPACK).
- A Conta do Google no seu dispositivo faz parte do programa de prévia para desenvolvedores do AICore.
- Seu dispositivo tem a versão mais recente do APK da prévia para desenvolvedores do AICore: no dispositivo, abra a página da Play Store do Android AICore e toque em Atualizar para instalar a versão mais recente do APK. Como alternativa, acesse as Configurações do dispositivo, pesquise por Serviços do sistema ou Serviços do Google, toque em Android AICore (Beta) e em Atualizar se houver uma atualização disponível.
- O dispositivo tem a versão mais recente do app Private Compute Services: no dispositivo, abra a página do Private Compute Services na Play Store e toque em Atualizar para instalar a versão mais recente do app. Se preferir, acesse Play Store > Gerenciar apps e dispositivos > Gerenciar > Private Compute Services e toque em Atualizar se houver uma atualização disponível.
Modelos e configuração compatíveis
A API Kit de ML Embedding usa a família de modelos EmbeddingGemma do Google, gerenciada pelo
Android AICore. Especificamente, a API Embedding usa o
EmbeddingGemma 2
(embedding_gemma_v2) para gerar embeddings de entradas de texto e imagem.
Confira as especificações e os limites de entrada:
| Propriedade de entrada | EmbeddingGemma 2 | Notas de uso |
|---|---|---|
| Modalidades aceitas | Texto e imagem (Modality.TEXT, Modality.IMAGE) |
Para confirmar se a entrada de imagem é aceita, verifique no momento da execução se model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) retorna true. |
| Limite de tokens | 256 tokens | Inspecione usando o método model.getTokenLimit(). Cada imagem é codificada em 70 tokens, deixando 186 tokens para o texto que a acompanha. |
| Resolução da imagem | Até 768 px no lado mais curto | O construtor da classe ImagePart aceita os tipos de argumento Bitmap, Uri ou ByteArray. As imagens com uma dimensão menor que 768 px são reduzidas automaticamente pelo SDK, preservando a proporção. Para otimizar o desempenho e o uso da memória, evite proporções extremas. |
A dimensão do vetor de saída é 768 (um objeto FloatArray de pontos flutuantes de 32 bits). É possível
inspecionar a dimensão do vetor de saída no momento da execução usando o
método model.getMetadata()?.dimension.
Primeiros passos
Para configurar seu projeto para usar a pesquisa semântica, adicione as seguintes dependências
ao arquivo build.gradle.kts (ou build.gradle) no módulo.
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'
// ...
}
Para recuperar os modelos disponíveis, use o
método EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()).
Definir um esquema do AppSearch
Para ativar a pesquisa de semelhança semântica, é necessário definir um esquema do AppSearch em que a propriedade do vetor esteja explicitamente configurada para indexação de semelhança.
// 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()
Indexar conteúdo do documento
Para indexar o conteúdo do documento, gere um embedding de vetor para cada
objeto Content, envolva-o em uma instância GenericDocument e salve-o no
banco de dados do AppSearch.
Ao criar um objeto EmbeddingVector, você também precisa transmitir o valor ativo de
modelSignature. O AppSearch usa essa string modelSignature para evitar
consultas de pesquisa incompatíveis.
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()
}
Executar pesquisa semântica
Para realizar uma consulta semântica, converta a consulta de pesquisa (texto e/ou imagem) em um vetor de embedding, configure os parâmetros de pesquisa vetorial e consulte o banco de dados do AppSearch.
Recomendações:
- Defina a métrica padrão como a
constante
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE. - Classifique os resultados pela fórmula
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - Use a string lógica
"semanticSearch(getEmbeddingParameter(0), $threshold)"para filtrar os resultados em que a similaridade atende ao valor limite (normalmente 0,5 ou mais).
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()
}
}
Usar um índice da Pesquisa híbrida
Para a melhor experiência do usuário, recomendamos o uso de uma estratégia híbrida que envolva pesquisa semântica e de palavras-chave. Quando o usuário estiver digitando ativamente em uma barra de pesquisa, use a pesquisa por palavra-chave para mostrar sugestões instantâneas de preenchimento automático. Quando ele inserir a consulta completa ou até mesmo em quebras de palavras, use a pesquisa semântica para ler, entender e buscar as melhores correspondências conceituais.
Reindexar o banco de dados de vetores
As embeddings geradas por diferentes versões de modelo residem em espaços vetoriais incompatíveis e não podem ser comparadas diretamente. É necessário reindexar o banco de dados do AppSearch sempre que você atualizar o modelo de embedding.
Recomendamos que você comece a reindexar os embeddings assim que o modelo que você está
usando entrar no período de descontinuação. Use a API Embedding para verificar o cronograma de descontinuação de um modelo. Chame o método model.getMetadata() para recuperar
o objeto EmbeddingMetadata e verifique essas propriedades de data do ciclo de vida
(retornadas como objetos LocalDate em UTC):
- O valor
deprecationDatemarca o início do período de descontinuação do modelo. Comece a reindexação enquanto o modelo ainda estiver funcionando e conclua o processo antes da desativação oficial. - O valor
shutdownDateé quando o modelo será desativado e não será mais disponibilizado. Após essa data, o modelo ficará inacessível para inferência, e os embeddings atuais dele serão forçados a serem reindexados com o modelo mais recente disponível.
Também comunicamos as próximas descontinuações de modelos, cronogramas de migração e datas de encerramento pelas notas da versão do Kit de ML e atualizações da documentação para desenvolvedores.
Práticas recomendadas
Para uma boa experiência do usuário, siga estas práticas ao integrar embeddings de vetor ao seu app Android:
- Programar cargas de trabalho de indexação: sempre programe cargas de trabalho de indexação em segundo plano usando a biblioteca WorkManager. Configure as tarefas em segundo plano para serem executadas apenas quando o dispositivo estiver carregando e inativo. O Kit de ML impõe limites de cota para o uso de modelos, semelhantes às APIs de LLM, com limites mais liberais quando o dispositivo está carregando. No modo de prévia para desenvolvedores, é possível ignorar as limitações de cota para fins de teste.
- Otimização de armazenamento e consulta: otimize o consumo de recursos do banco de dados e
a latência de consulta usando quantização e truncamento de dimensionalidade:
- Configure o esquema
AppSearchSchemapara usar a constanteQUANTIZATION_TYPE_8_BIT, conforme mostrado em Definir um esquema do AppSearch. Isso reduz o uso da memória com impacto mínimo na qualidade, diminuindo a precisão de cada coordenada para um número inteiro de 8 bits. - Use o aprendizado de representações Matryoshka (MRL, na sigla em inglês), compatível com o modelo EmbeddingGemma 2, para truncar o vetor de embedding. Para saber mais, consulte EmbeddingGemma 2 no HuggingFace.
- Configure o esquema
- Consistência de assinatura: não consulte um espaço vetorial usando uma assinatura incompatível. Se você fizer upgrade da versão do modelo de embedding, será necessário limpar o banco de dados do AppSearch e gerar novamente os embeddings para todos os documentos usando a nova assinatura do modelo.
- Segurança e moderação de conteúdo: você é responsável por implementar a moderação de conteúdo e a filtragem de segurança no seu app. Ao contrário das APIs de texto gerado ou imagem generativas, a API Embedding do Kit de ML não inclui filtros de segurança integrados. Como a saída do modelo gera representações de vetores numéricos
(coordenadas
FloatArray) de entradas fornecidas pelo autor da chamada, em vez de gerar novos textos ou mídias, os vetores numéricos não apresentam riscos de segurança de conteúdo generativo. Omitir a filtragem de segurança no dispositivo também preserva o desempenho de indexação e pesquisa de baixa latência, evitando a contenção de CPU durante a indexação de documentos de alta capacidade de processamento e as consultas em tempo real. Para melhorar a segurança, siga estas diretrizes:- Moderar entradas não confiáveis e conteúdo indexado: se o app indexar ou pesquisar conteúdo não confiável gerado pelo usuário ou dados externos, aplique moderação de conteúdo e filtros de política ao texto ou às imagens de origem antes de indexá-los ou exibir resultados da pesquisa.
- Proteja os pipelines de IA generativa downstream: se os documentos ou trechos recuperados forem transmitidos como contexto para um modelo de IA generativa downstream, verifique se os filtros de segurança e as políticas de conteúdo estão ativados nesse modelo downstream e na saída voltada ao usuário.