Semantische Suche mit der Embeddings API

In diesem Leitfaden wird beschrieben, wie Sie die semantische Suche auf dem Gerät implementieren. Bei dieser Suchtechnik werden relevante Inhalte anhand der Bedeutung der Suchanfrage und nicht nur anhand von Suchbegriffen gefunden. Bei der semantischen Suche müssen Sie keine benutzerdefinierten Synonymtabellen oder Tagging-Taxonomien mehr manuell erstellen und verwalten. Verwenden Sie die folgenden Bibliotheken, um die semantische Suche auf dem Gerät zu implementieren:

  • ML Kit SDK, insbesondere die Embeddings API, um Vektoreinbettungen zu generieren
  • AndroidX AppSearch, um die Vektoreinbettungen zu speichern und strukturierte Daten mithilfe von Kosinus-Distanzmetriken zu durchsuchen, um Inhalte anhand ihrer Semantik zu finden

Ein funktionierendes Beispiel finden Sie in der ML Kit GenAI-Beispiel-App auf GitHub.

Vorbereitung

Damit Sie die semantische Suche auf dem Gerät verwenden können, müssen die folgenden Geräte- und Kontoanforderungen erfüllt sein:

  • Ihr Gerät unterstützt Android AICore. Die meisten von AICore unterstützten Geräte verwenden NPU-beschleunigte Modelle, während die übrigen unterstützten Geräte auf ein CPU-Modell (mit XNNPACK-Beschleunigung) zurückgreifen.
  • Das Google-Konto auf deinem Gerät ist Teil des AICore-Entwicklervorschau-Programms.
  • Auf Ihrem Gerät ist die aktuelle Version des AICore Entwicklervorschau-APK installiert: Öffnen Sie auf Ihrem Gerät die Android AICore Google Play Store-Seite und tippen Sie auf Aktualisieren, um die aktuelle Version des APK zu installieren. Alternativ kannst du die Einstellungen deines Geräts öffnen, nach Systemdienste oder Google-Dienste suchen, auf Android AICore (Beta) tippen und dann auf Aktualisieren tippen, falls ein Update verfügbar ist.
  • Auf deinem Gerät ist die aktuelle Version der Private Compute Services App installiert: Öffne auf deinem Gerät die Google Play Store-Seite für Private Compute Services und tippe auf Aktualisieren, um die aktuelle Version der App zu installieren. Alternativ kannst du auch den Google Play Store > „Apps und Gerät verwalten“ > „Verwalten“ > „Private Compute Services“ aufrufen und auf Aktualisieren tippen, wenn ein Update verfügbar ist.

Unterstützte Modelle und Konfigurationen

Die ML Kit Embeddings API verwendet die EmbeddingGemma-Modellfamilie von Google, die von Android AICore verwaltet wird. Die Embeddings API verwendet EmbeddingGemma 2 (embedding_gemma_v2), um Einbettungen für Text- und Bildeingaben zu generieren.

Hier sind die Eingabespezifikationen und ‑limits:

Eingabeattribut EmbeddingGemma 2 Verwendungshinweise
Unterstützte Modalitäten Text und Bild (Modality.TEXT, Modality.IMAGE) Prüfen Sie zur Laufzeit, ob model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) den Wert true zurückgibt, um zu bestätigen, dass die Bildeingabe unterstützt wird.
Tokenlimit 256 Tokens Prüfen Sie die Informationen mit der Methode model.getTokenLimit(). Jedes Bild wird in 140 Soft-Tokens codiert, sodass 116 Tokens für den begleitenden Text übrig bleiben.
Bildauflösung Bis zu 768 px auf der kürzeren Seite Der Konstruktor für die Klasse ImagePart akzeptiert die Argumenttypen Bitmap, Uri oder ByteArray. Bilder mit einer kürzeren Dimension von mehr als 768 Pixeln werden vom SDK automatisch herunterskaliert, wobei das Seitenverhältnis beibehalten wird. Vermeiden Sie extreme Seitenverhältnisse, um eine optimale Leistung und Speichernutzung zu erzielen.

Die Dimension des Ausgabevektors ist 768 (ein FloatArray-Objekt mit 32-Bit-Gleitkommazahlen). Sie können die Dimension des Ausgabevektors zur Laufzeit mit der Methode model.getMetadata()?.dimension prüfen.

Jetzt starten

Wenn Sie Ihr Projekt für die semantische Suche konfigurieren möchten, fügen Sie der Datei build.gradle.kts (oder build.gradle) auf Modulebene die folgenden Abhängigkeiten hinzu.

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'
    // ...
}

Einbettungsmodell abrufen

Verwenden Sie EmbeddingGeneration.getEmbeddingModels(), um die auf dem Gerät verfügbaren Einbettungsmodelle aufzulisten, und wählen Sie eines anhand seiner Modellsignatur aus. Schließen Sie alle Modelle, die Sie nicht verwenden.

// 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
}

Wenn Sie ein bestimmtes Modell abrufen möchten, übergeben Sie die Signatur: EmbeddingGeneration.getEmbeddingModels("embedding_gemma_v2").

Laden Sie das Modell vor dem Generieren von Einbettungen herunter, falls erforderlich, und wärmen Sie es auf:

suspend fun prepareModel(model: EmbeddingModel): Boolean {
    when (model.checkStatus()) {
        FeatureStatus.UNAVAILABLE -> return false
        FeatureStatus.DOWNLOADABLE, FeatureStatus.DOWNLOADING -> model.download().collect()
    }
    model.warmup()
    return true
}

Rufen Sie model.close() auf, wenn Sie das Modell nicht mehr benötigen.

AppSearch-Schema definieren

Wenn Sie die Suche nach semantischer Ähnlichkeit aktivieren möchten, müssen Sie ein AppSearch-Schema definieren, in dem die Vektoreigenschaft explizit für die Ähnlichkeitsindexierung konfiguriert ist.

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()

Dokumentinhalt indexieren

Wenn Sie Dokumentinhalte indexieren möchten, müssen Sie für jedes Content-Objekt eine Vektoreinbettung generieren, diese in eine GenericDocument-Instanz verpacken und in Ihrer AppSearch-Datenbank speichern.

Beim Erstellen eines EmbeddingVector-Objekts müssen Sie auch den aktiven modelSignature-Wert übergeben. AppSearch verwendet diesen modelSignature-String, um inkompatible Suchanfragen zu verhindern.

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()
}

Wenn Sie eine semantische Abfrage ausführen möchten, konvertieren Sie die Suchanfrage (Text und/oder Bild) in einen Einbettungsvektor, konfigurieren Sie die Parameter für die Vektorsuche und fragen Sie Ihre AppSearch-Datenbank ab.

Empfehlungen:

  • AppSearch verwendet standardmäßig den Kosinus-Messwert (SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE), sodass Sie ihn nicht festlegen müssen.
  • Ergebnisse nach der Formel "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))" sortieren.
  • Verwenden Sie den logischen String "semanticSearch(getEmbeddingParameter(0), $threshold)", um Ergebnisse zu filtern, bei denen die Ähnlichkeit Ihren Schwellenwert erfüllt (normalerweise 0,5 oder höher).
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()
    }
}

Hybridsuchindex verwenden

Für eine optimale Nutzererfahrung empfehlen wir eine hybride Strategie, die sowohl die semantische Suche als auch die Keyword-Suche umfasst. Wenn der Nutzer aktiv in eine Suchleiste tippt, verwenden Sie die Keyword-Suche, um sofort Vorschläge für die automatische Vervollständigung anzuzeigen. Wenn er die vollständige Abfrage eingibt oder sogar bei Wortpausen, verwenden Sie die semantische Suche, um die besten konzeptionellen Übereinstimmungen zu lesen, zu verstehen und abzurufen.

Vektordatenbank neu indexieren

Einbettungen, die von verschiedenen Modellversionen generiert werden, befinden sich in inkompatiblen Vektorräumen und können nicht direkt verglichen werden. Sie müssen Ihre AppSearch-Datenbank jedes Mal neu indexieren, wenn Sie das Einbettungsmodell aktualisieren.

Wir empfehlen, mit der Neuindexierung der Einbettungen zu beginnen, sobald das von Ihnen verwendete Modell in die Phase der Einstellung eintritt. Mit der Embeddings API können Sie den Zeitplan für die Einstellung eines Modells prüfen. Rufen Sie die Methode model.getMetadata() auf, um das Objekt EmbeddingMetadata abzurufen, und prüfen Sie die folgenden Lebenszyklus-Datumseigenschaften (als LocalDate-Objekte in UTC zurückgegeben):

  • Der Wert deprecationDate markiert den Beginn des Einstellungszeitraums des Modells. Sie sollten mit der Neuindexierung beginnen, während das Modell noch funktioniert, und die Neuindexierung vor der offiziellen Einstellung abschließen.
  • Der Wert shutdownDate gibt an, wann das Modell außer Betrieb genommen und nicht mehr bereitgestellt wird. Nach diesem Datum ist das Modell für die Inferenz nicht mehr verfügbar und vorhandene Einbettungen dieses Modells müssen mit dem neuesten verfügbaren Modell neu indexiert werden.

Außerdem informieren wir über anstehende Modelleinstellungen, Migrationszeitpläne und geplante Abschaltdaten in den Versionshinweisen für ML Kit und in den Updates der Entwicklerdokumentation.

Best Practices

Für eine gute Nutzererfahrung sollten Sie die folgenden Best Practices beachten, wenn Sie Vektoreinbettungen in Ihre Android-App einbinden:

  • Indexierungsarbeitslasten planen: Planen Sie Hintergrundindexierungsarbeitslasten immer mit der WorkManager-Bibliothek. Konfiguriere deine Hintergrundaufgaben so, dass sie nur ausgeführt werden, wenn das Gerät geladen wird und inaktiv ist. ML Kit legt Kontingentlimits für die Modellnutzung fest, ähnlich wie LLM APIs. Wenn das Gerät geladen wird, sind die Limits großzügiger. Im Entwicklervorschaumodus können Sie zu Testzwecken Kontingentbeschränkungen umgehen.
  • Speicher- und Abfrageoptimierung: Optimieren Sie den Speicherbedarf Ihrer Datenbank und die Abfragelatenz mithilfe von Quantisierung und Dimensionskürzung:
    • Konfiguriere dein AppSearchSchema-Schema so, dass die Konstante QUANTIZATION_TYPE_8_BIT verwendet wird, wie unter AppSearch-Schema definieren beschrieben. Dadurch wird die Speichernutzung bei minimalen Auswirkungen auf die Qualität reduziert, da die Genauigkeit jeder Koordinate auf eine 8-Bit-Ganzzahl verringert wird.
    • Verwenden Sie Matryoshka Representation Learning (MRL), das vom EmbeddingGemma 2-Modell unterstützt wird, um den Einbettungsvektor zu kürzen. Weitere Informationen finden Sie unter EmbeddingGemma 2 auf HuggingFace.
  • Konsistenz der Signatur: Fragen Sie einen Vektorraum nicht mit einer nicht übereinstimmenden Signatur ab. Wenn Sie die Version des Einbettungsmodells aktualisieren, müssen Sie die AppSearch-Datenbank löschen und Einbettungen für alle Dokumente mit der neuen Modellsignatur neu generieren.
  • Sicherheit und Inhaltsmoderation: Sie sind für die Implementierung von Inhaltsmoderation und Sicherheitsfiltern in Ihrer App verantwortlich. Im Gegensatz zu APIs für generative Texte oder Bilder enthält die ML Kit Embeddings API keine integrierten Sicherheitsfilter. Da das Modell numerische Vektordarstellungen (FloatArray-Koordinaten) der vom Aufrufer bereitgestellten Eingabe ausgibt, anstatt neuen Text oder neue Medien zu generieren, stellen numerische Vektoren von Natur aus keine Sicherheitsrisiken für generative Inhalte dar. Wenn Sie die Sicherheitsfilterung auf dem Gerät weglassen, bleiben auch die Indexierung mit niedriger Latenz und die Suchleistung erhalten, da CPU-Konflikte während der Dokumentindexierung mit hohem Durchsatz und Echtzeitabfragen vermieden werden. Beachten Sie die folgenden Richtlinien, um die Sicherheit zu verbessern:
    • Nicht vertrauenswürdige Eingaben und indexierte Inhalte moderieren: Wenn Ihre Anwendung nicht vertrauenswürdige, von Nutzern erstellte Inhalte oder externe Daten indexiert oder durchsucht, sollten Sie die Moderation von Inhalten und Richtlinienfilter auf den Quelltext oder die Bilder anwenden, bevor Sie sie indexieren oder Suchergebnisse anzeigen.
    • Nachgelagerte generative KI-Pipelines schützen: Wenn abgerufene Dokumente oder Passagen als Kontext an ein nachgelagertes generatives KI-Modell übergeben werden, prüfen Sie, ob Sicherheitsfilter und Inhaltsrichtlinien für dieses nachgelagerte Modell und seine für Nutzer sichtbare Ausgabe aktiviert sind.

Zusätzliche Ressourcen