Semantische Suche

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 Embedding 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

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 der AICore-Entwicklervorschau.
  • 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, falls ein Update verfügbar ist.
  • Auf Ihrem Gerät ist die aktuelle Version der Private Compute Services App installiert: Öffnen Sie auf Ihrem Gerät die Google Play Store-Seite für Private Compute Services und tippen Sie auf Aktualisieren, um die aktuelle Version der App zu installieren. Alternativ können Sie auch zu Google Play Store > Apps und Gerät verwalten > Verwalten > Private Compute Services gehen und auf Aktualisieren tippen, falls ein Update verfügbar ist.

Unterstützte Modelle und Konfigurationen

Die ML Kit Embedding API verwendet die EmbeddingGemma-Modellfamilie von Google, die von Android AICore verwaltet wird. Die Embedding 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) Wenn Sie prüfen möchten, ob die Bildeingabe unterstützt wird, prüfen Sie zur Laufzeit, ob model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) den Wert true zurückgibt.
Tokenlimit 256 Tokens Prüfen Sie die Informationen mit der Methode model.getTokenLimit(). Jedes Bild wird in 70 Tokens codiert, sodass 186 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'
    // ...
}

Verwenden Sie die Methode EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()), um die verfügbaren Modelle abzurufen.

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.

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

Dokumentinhalt indexieren

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

Wenn Sie ein EmbeddingVector-Objekt erstellen, müssen Sie auch den aktiven modelSignature-Wert übergeben. AppSearch verwendet diesen modelSignature-String, um inkompatible Suchanfragen zu verhindern.

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

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:

  • Legen Sie den Standardmesswert auf die Konstante SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE fest.
  • 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).
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()
    }
}

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 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 Vektorbereichen 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 Embedding 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-Datumsattribute (als LocalDate-Objekte in UTC zurückgegeben):

  • Der Wert deprecationDate markiert den Beginn des Zeitraums, in dem das Modell eingestellt wird. 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 nicht mehr für Inferenzen 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 Termine für die Einstellung 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. Das ML Kit legt Kontingentlimits für die Modellnutzung fest, ähnlich wie LLM APIs, wobei die Limits großzügiger sind, wenn das Gerät geladen wird. Im Entwicklervorschaumodus können Sie Kontingentbeschränkungen zu Testzwecken umgehen.
  • Speicher- und Abfrageoptimierung: Optimieren Sie den Speicherbedarf Ihrer Datenbank und die Abfragelatenz mithilfe von Quantisierung und Dimensionskürzung:
    • Konfigurieren Sie Ihr 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 Embedding API keine integrierten Sicherheitsfilter. Da die Modellausgabe numerische Vektordarstellungen (FloatArray-Koordinaten) der vom Aufrufer bereitgestellten Eingabe ist, anstatt neuen Text oder neue Medien zu generieren, stellen numerische Vektoren keine inhärenten Sicherheitsrisiken für generative Inhalte dar. Wenn Sie die Sicherheitsfilterung auf dem Gerät weglassen, bleiben auch die Indexierungs- und Suchleistung mit niedriger Latenz erhalten, da CPU-Konflikte während der Dokumentindexierung mit hohem Durchsatz und Echtzeitabfragen vermieden werden. Beachte die folgenden Richtlinien, um die Sicherheit zu verbessern:
    • Nicht vertrauenswürdige Eingaben und indexierte Inhalte moderieren: Wenn Ihre App nicht vertrauenswürdige, von Nutzern erstellte Inhalte oder externe Daten indexiert oder durchsucht, wenden Sie Inhaltsmoderation und Richtlinienfilter auf den Quelltext oder die Bilder an, 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