Questa guida illustra come implementare la ricerca semantica on-device, una tecnica di ricerca che trova contenuti pertinenti in base al significato della query, anziché solo in base alle parole chiave. La ricerca semantica elimina la necessità di creare e gestire manualmente tabelle di sinonimi personalizzate o tassonomie di tagging. Per implementare la ricerca semantica on-device, utilizza le seguenti librerie:
- SDK ML Kit, in particolare l'API Embedding, per generare vector embedding
- AndroidX AppSearch per archiviare i vector embedding e cercare dati strutturati utilizzando metriche di distanza del coseno per trovare i contenuti in base alla loro semantica
Prerequisiti
Per utilizzare la ricerca semantica on-device, devi soddisfare i seguenti requisiti relativi al dispositivo e all'account:
- Il tuo dispositivo supporta Android AICore. La maggior parte dei dispositivi supportati da AICore utilizza modelli con accelerazione NPU, mentre i restanti dispositivi supportati utilizzano un modello CPU (con accelerazione XNNPACK).
- L'Account Google sul tuo dispositivo fa parte del programma Anteprima per gli sviluppatori AICore.
- Il tuo dispositivo ha l'ultima versione dell'APK Anteprima per gli sviluppatori di AICore: sul dispositivo, apri la pagina del Play Store di Android AICore e tocca Aggiorna per installare l'ultima versione dell'APK. In alternativa, vai alle Impostazioni del dispositivo, cerca Servizi di sistema o Servizi Google, tocca Android AICore (Beta) e tocca Aggiorna se è disponibile un aggiornamento.
- Il tuo dispositivo ha l'ultima versione dell'app Private Compute Services: sul tuo dispositivo, apri la pagina del Play Store di Private Compute Services e tocca Aggiorna per installare l'ultima versione dell'app. In alternativa, vai su Play Store > Gestisci app e dispositivo > Gestisci > Private Compute Services e tocca Aggiorna se è disponibile un aggiornamento.
Modelli e configurazione supportati
L'API ML Kit Embedding utilizza la famiglia di modelli EmbeddingGemma di Google, gestita da Android AICore. In particolare, l'API Embedding utilizza
EmbeddingGemma 2
(embedding_gemma_v2) per generare embedding per input di testo e immagini.
Ecco le specifiche e i limiti di input:
| Proprietà di input | EmbeddingGemma 2 | Note sull'utilizzo |
|---|---|---|
| Modalità supportate | Testo e immagine (Modality.TEXT, Modality.IMAGE) |
Per verificare che l'input di immagini sia supportato, controlla in fase di runtime che model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) restituisca true. |
| Limite di token | 256 token | Esegui l'ispezione utilizzando il metodo model.getTokenLimit(). Ogni immagine viene codificata in 70 token, lasciando 186 token per il testo di accompagnamento. |
| Risoluzione immagine | Fino a 768 px sul lato più corto | Il costruttore della classe ImagePart accetta i tipi di argomenti Bitmap, Uri o ByteArray. Le immagini con una dimensione più piccola superiore a 768 px vengono ridimensionate automaticamente dall'SDK, mantenendo le proporzioni. Per prestazioni e memoria utilizzata ottimali, evita proporzioni estreme. |
La dimensione del vettore di output è 768 (un oggetto FloatArray di numeri in virgola mobile a 32 bit). Puoi
ispezionare la dimensione del vettore di output in fase di runtime utilizzando il
metodo model.getMetadata()?.dimension.
Inizia
Per configurare il progetto in modo che utilizzi la ricerca semantica, aggiungi le seguenti dipendenze al file build.gradle.kts (o build.gradle) a livello di modulo.
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'
// ...
}
Per recuperare i modelli disponibili, utilizza il metodo EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()).
Definisci uno schema AppSearch
Per abilitare la ricerca di somiglianza semantica, devi definire uno schema AppSearch in cui la proprietà vector è configurata esplicitamente per l'indicizzazione della somiglianza.
// 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()
Indicizza i contenuti dei documenti
Per indicizzare i contenuti dei documenti, devi generare un vector embedding per ogni oggetto Content, inserirlo in un'istanza GenericDocument e salvarlo nel database AppSearch.
Quando crei un oggetto EmbeddingVector, devi anche passare il valore modelSignature attivo. AppSearch utilizza questa stringa modelSignature per impedire
query di ricerca incompatibili.
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()
}
Esegui la ricerca semantica
Per eseguire una query semantica, converti la query di ricerca (testo e/o immagine) in un vettore di embedding, configura i parametri di ricerca vettoriale ed esegui una query sul database AppSearch.
Consigli:
- Imposta la metrica predefinita sulla costante
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE. - Classifica i risultati in base alla formula
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - Utilizza la stringa logica
"semanticSearch(getEmbeddingParameter(0), $threshold)"per filtrare i risultati in cui la somiglianza soddisfa il valore di soglia (in genere 0,5 o superiore).
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()
}
}
Utilizza un indice della Ricerca ibrido
Per un'esperienza utente ottimale, ti consigliamo di utilizzare una strategia ibrida che preveda la ricerca sia semantica sia per parole chiave. Quando l'utente digita attivamente in una barra di ricerca, utilizza la ricerca per parole chiave per mostrare suggerimenti di completamento automatico istantanei. Quando inserisce la query completata o anche in corrispondenza di interruzioni di parole, utilizza la ricerca semantica per leggere, comprendere e recuperare le migliori corrispondenze concettuali.
Reindicizza il database vettoriale
Gli embedding generati da versioni diverse del modello risiedono in spazi vettoriali incompatibili e non possono essere confrontati direttamente. Devi reindicizzare il database AppSearch ogni volta che aggiorni il modello di embedding.
Ti consigliamo di iniziare a reindicizzare gli embedding non appena il modello che stai utilizzando entra nel periodo di ritiro. Puoi utilizzare l'API Embedding per controllare la pianificazione del ritiro di un modello. Chiama il metodo model.getMetadata() per recuperare l'oggetto EmbeddingMetadata e controlla queste proprietà della data del ciclo di vita (restituite come oggetti LocalDate in UTC):
- Il valore
deprecationDateindica l'inizio del periodo di ritiro del modello. Devi avviare la reindicizzazione mentre il modello continua a funzionare e completarla prima della sua disattivazione ufficiale. - Il valore
shutdownDateindica quando il modello verrà ritirato e non verrà più pubblicato. Dopo questa data, il modello non è accessibile per l'inferenza e gli embedding esistenti di questo modello vengono forzati a eseguire nuovamente l'indicizzazione con l'ultimo modello disponibile.
Comunichiamo anche le imminenti deprecazioni dei modelli, le tempistiche di migrazione e le date di disattivazione target tramite le note di rilascio di ML Kit e gli aggiornamenti della documentazione per lo sviluppo.
Best practice
Per un'esperienza utente ottimale, segui queste pratiche durante l'integrazione dei vector embedding nella tua app per Android:
- Pianifica i workload di indicizzazione: pianifica sempre i workload di indicizzazione in background utilizzando la libreria WorkManager. Configura le attività in background in modo che vengano eseguite solo quando il dispositivo è in carica e inattivo. ML Kit impone limiti di quota per l'utilizzo dei modelli, simili alle API LLM, con limiti più ampi quando il dispositivo è in carica. In modalità Anteprima per gli sviluppatori, puoi bypassare le limitazioni delle quote per scopi di test.
- Ottimizzazione dello spazio di archiviazione e delle query: ottimizza l'impatto del database e la latenza delle query utilizzando la quantizzazione e il troncamento della dimensionalità:
- Configura lo schema
AppSearchSchemain modo che utilizzi la costanteQUANTIZATION_TYPE_8_BIT, come mostrato in Definisci uno schema AppSearch. In questo modo, la memoria utilizzata viene ridotta con un impatto minimo sulla qualità, riducendo la precisione di ogni coordinata a un numero intero a 8 bit. - Utilizza il representation learning Matryoshka (MRL), supportato dal modello EmbeddingGemma 2, per troncare il vettore di embedding. Per saperne di più, consulta EmbeddingGemma 2 su HuggingFace.
- Configura lo schema
- Coerenza della firma: non eseguire query su uno spazio vettoriale utilizzando una firma non corrispondente. Se esegui l'upgrade della versione del modello di embedding, devi cancellare il database AppSearch e rigenerare gli embedding per tutti i documenti utilizzando la nuova firma del modello.
- Sicurezza e moderazione dei contenuti: è tua responsabilità implementare la moderazione dei contenuti e i filtri di sicurezza nella tua app. A differenza delle API di testo o immagini generative, l'API ML Kit Embedding non include filtri di sicurezza integrati. Poiché l'output del modello restituisce rappresentazioni vettoriali numeriche (coordinate
FloatArray) dell'input fornito dal chiamante anziché generare nuovo testo o contenuti multimediali, i vettori numerici non presentano intrinsecamente rischi per la sicurezza dei contenuti generativi. L'omissione dei filtri di sicurezza on-device preserva anche le prestazioni di indicizzazione e ricerca a bassa latenza, evitando la contesa della CPU durante l'indicizzazione dei documenti a throughput elevato e le query in tempo reale. Per migliorare la sicurezza, segui queste linee guida:- Modera gli input non attendibili e i contenuti indicizzati: se la tua app indicizza o cerca contenuti generati dagli utenti non attendibili o dati esterni, applica filtri di moderazione dei contenuti e delle norme al testo o alle immagini di origine prima di indicizzarli o visualizzare i risultati di ricerca.
- Proteggi le pipeline di AI generativa downstream: se i documenti o i passaggi recuperati vengono passati come contesto a un modello di AI generativa downstream, verifica che i filtri di sicurezza e le policy relative ai contenuti siano abilitati su quel modello downstream e sul suo output rivolto agli utenti.