Penelusuran semantik

Panduan ini akan memandu Anda menerapkan penelusuran semantik di perangkat, yaitu teknik penelusuran yang menemukan konten relevan berdasarkan makna kueri, bukan hanya berdasarkan kata kunci. Penelusuran semantik menghilangkan kebutuhan untuk membuat dan memelihara tabel sinonim kustom atau taksonomi pemberian tag secara manual. Untuk menerapkan penelusuran semantik di perangkat, gunakan library berikut:

  • ML Kit SDK, khususnya Embedding API, untuk menghasilkan embedding vektor
  • AndroidX AppSearch untuk menyimpan embedding vektor, dan menelusuri data terstruktur menggunakan metrik jarak kosinus untuk menemukan konten berdasarkan semantiknya

Prasyarat

Untuk menggunakan penelusuran semantik di perangkat, Anda harus memenuhi persyaratan perangkat dan akun berikut:

  • Perangkat Anda mendukung Android AICore. Sebagian besar perangkat yang didukung AICore menggunakan model yang diakselerasi NPU, sedangkan perangkat lainnya yang didukung akan beralih ke model CPU (dengan akselerasi XNNPACK).
  • Akun Google di perangkat Anda adalah bagian dari program Pratinjau Developer AICore.
  • Perangkat Anda memiliki versi terbaru APK Pratinjau Developer AICore: Di perangkat, buka halaman Google Play Store Android AICore dan ketuk Update untuk menginstal versi terbaru APK. Atau, buka Setelan perangkat, telusuri Layanan sistem atau Layanan Google, ketuk Android AICore (Beta), lalu ketuk Update jika update tersedia.
  • Perangkat Anda memiliki versi terbaru aplikasi Private Compute Services: Di perangkat Anda, buka halaman Google Play Store Private Compute Services dan ketuk Update untuk menginstal versi terbaru aplikasi. Atau, buka Google Play Store > Kelola aplikasi & perangkat > Kelola > Private Compute Services dan ketuk Update jika update tersedia.

Model dan konfigurasi yang didukung

Embedding API ML Kit menggunakan rangkaian model EmbeddingGemma Google, yang dikelola oleh Android AICore. Secara khusus, Embedding API menggunakan EmbeddingGemma 2 (embedding_gemma_v2) untuk menghasilkan embedding untuk input teks dan gambar.

Berikut adalah spesifikasi dan batasan input:

Properti input EmbeddingGemma 2 Catatan penggunaan
Modalitas yang didukung Teks dan gambar (Modality.TEXT, Modality.IMAGE) Untuk mengonfirmasi bahwa input gambar didukung, periksa saat runtime bahwa model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) menampilkan true.
Token limit (batas token) 256 token Periksa menggunakan metode model.getTokenLimit(). Setiap gambar dienkode menjadi 70 token, sehingga menyisakan 186 token untuk teks yang menyertainya.
Resolusi gambar Hingga 768 px di sisi yang lebih pendek Konstruktor untuk class ImagePart menerima jenis argumen Bitmap, Uri, atau ByteArray. Gambar dengan dimensi yang lebih pendek dan lebih besar dari 768 px akan otomatis diperkecil oleh SDK dengan tetap mempertahankan rasio aspek. Untuk performa dan penggunaan memori yang optimal, hindari rasio aspek ekstrem.

Dimensi vektor output adalah 768 (objek FloatArray dari float 32-bit). Anda dapat memeriksa dimensi vektor output saat runtime menggunakan metode model.getMetadata()?.dimension.

Mulai

Untuk mengonfigurasi project agar menggunakan penelusuran semantik, tambahkan dependensi berikut ke file build.gradle.kts (atau build.gradle) level modul Anda.

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

Untuk mengambil model yang tersedia, gunakan metode EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()).

Menentukan skema AppSearch

Untuk mengaktifkan penelusuran kesamaan semantik, Anda harus menentukan skema AppSearch tempat properti vektor dikonfigurasi secara eksplisit untuk pengindeksan kesamaan.

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

Mengindeks konten dokumen

Untuk mengindeks konten dokumen, Anda harus membuat embedding vektor untuk setiap objek Content, menggabungkannya dalam instance GenericDocument, dan menyimpannya ke database AppSearch.

Saat membuat objek EmbeddingVector, Anda juga harus meneruskan nilai modelSignature yang aktif. AppSearch menggunakan string modelSignature ini untuk mencegah kueri penelusuran yang tidak kompatibel.

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

Untuk melakukan kueri semantik, konversi kueri penelusuran (teks dan/atau gambar) menjadi vektor embedding, konfigurasikan parameter penelusuran vektor, dan buat kueri database AppSearch Anda.

Rekomendasi:

  • Tetapkan metrik default ke konstanta SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE.
  • Memberi peringkat hasil berdasarkan formula "sum(this.matchedSemanticScores(getEmbeddingParameter(0)))".
  • Gunakan string "semanticSearch(getEmbeddingParameter(0), $threshold)" logis untuk memfilter hasil yang kemiripannya memenuhi nilai minimum Anda (biasanya 0,5 atau lebih tinggi).
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()
    }
}

Menggunakan indeks penelusuran campuran

Untuk pengalaman pengguna terbaik, sebaiknya gunakan strategi hibrid yang melibatkan penelusuran semantik dan kata kunci. Saat pengguna aktif mengetik di kotak penelusuran, gunakan penelusuran kata kunci untuk menampilkan saran pelengkapan otomatis instan; saat mereka memasukkan kueri lengkap, atau bahkan saat jeda kata, gunakan penelusuran semantik untuk membaca, memahami, dan mengambil kecocokan konseptual terbaik.

Mengindeks ulang database vektor Anda

Embedding yang dihasilkan oleh versi model yang berbeda berada dalam ruang vektor yang tidak kompatibel dan tidak dapat dibandingkan secara langsung. Anda harus mengindeks ulang database AppSearch setiap kali mengupdate model embedding.

Sebaiknya mulai mengindeks ulang embedding segera setelah model yang Anda gunakan memasuki periode penghentian penggunaan. Anda dapat menggunakan Embedding API untuk memeriksa jadwal penghentian penggunaan model. Panggil metode model.getMetadata() untuk mengambil objek EmbeddingMetadata dan periksa properti tanggal siklus proses ini (ditampilkan sebagai objek LocalDate dalam UTC):

  • Nilai deprecationDate menandai awal periode penghentian penggunaan model. Anda harus mulai mengindeks ulang saat model terus berfungsi dan menyelesaikan pengindeksan ulang sebelum penonaktifan resminya.
  • Nilai shutdownDate adalah waktu saat model akan dinonaktifkan dan tidak lagi disajikan. Setelah tanggal ini, model tidak dapat diakses untuk inferensi dan embedding yang ada dari model ini akan dipaksa untuk diindeks ulang dengan model terbaru yang tersedia.

Kami juga menginformasikan penghentian penggunaan model mendatang, linimasa migrasi, dan target tanggal penonaktifan melalui catatan rilis ML Kit dan update dokumentasi developer.

Praktik terbaik

Untuk pengalaman pengguna yang baik, ikuti praktik berikut saat mengintegrasikan embedding vektor ke dalam aplikasi Android Anda:

  • Menjadwalkan workload pengindeksan: Selalu jadwalkan workload pengindeksan latar belakang menggunakan library WorkManager. Konfigurasikan tugas latar belakang Anda agar hanya berjalan saat perangkat sedang mengisi daya dan tidak ada aktivitas. ML Kit menerapkan batas kuota untuk penggunaan model, mirip dengan API LLM, dengan batas yang lebih longgar saat perangkat sedang diisi dayanya. Dalam mode Pratinjau Developer, Anda dapat mengabaikan batasan kuota untuk tujuan pengujian.
  • Pengoptimalan penyimpanan dan kueri: Optimalkan jejak database dan latensi kueri Anda menggunakan kuantisasi dan pemotongan dimensi:
    • Konfigurasikan skema AppSearchSchema Anda untuk menggunakan konstanta QUANTIZATION_TYPE_8_BIT, seperti yang ditunjukkan di Menentukan skema AppSearch. Hal ini mengurangi penggunaan memori dengan dampak minimal pada kualitas dengan memperkecil presisi setiap koordinat menjadi bilangan bulat 8-bit.
    • Gunakan Matryoshka Representation Learning (MRL), yang didukung oleh model EmbeddingGemma 2, untuk memangkas vektor embedding. Untuk mempelajari lebih lanjut, lihat EmbeddingGemma 2 di HuggingFace.
  • Konsistensi tanda tangan: Jangan mengkueri ruang vektor menggunakan tanda tangan yang tidak cocok. Jika mengupgrade versi model embedding, Anda harus menghapus total database AppSearch dan membuat ulang embedding untuk semua dokumen menggunakan tanda tangan model baru.
  • Keamanan dan moderasi konten: Anda bertanggung jawab untuk menerapkan moderasi konten dan pemfilteran keamanan di aplikasi Anda. Tidak seperti API teks atau gambar generatif, Embedding API ML Kit tidak menyertakan filter keamanan bawaan. Karena model menghasilkan representasi vektor numerik (koordinat FloatArray) dari input yang diberikan pemanggil, bukan menghasilkan teks atau media baru, vektor numerik tidak secara inheren menimbulkan risiko keamanan konten generatif. Menghilangkan pemfilteran keamanan di perangkat juga mempertahankan performa pengindeksan dan penelusuran latensi rendah dengan menghindari pertentangan CPU selama pengindeksan dokumen dengan throughput tinggi dan kueri real-time. Untuk meningkatkan keamanan, ikuti panduan berikut:
    • Moderasi input yang tidak tepercaya dan konten yang diindeks: Jika aplikasi Anda mengindeks atau menelusuri konten buatan pengguna yang tidak tepercaya atau data eksternal, terapkan moderasi konten dan filter kebijakan ke teks atau gambar sumber sebelum mengindeksnya atau menampilkan hasil penelusuran.
    • Lindungi pipeline AI generatif downstream: Jika dokumen atau bagian yang diambil diteruskan sebagai konteks ke model AI generatif downstream, pastikan filter keamanan dan kebijakan konten diaktifkan pada model downstream tersebut dan output-nya yang ditampilkan kepada pengguna.

Referensi lainnya