Z tego przewodnika dowiesz się, jak wdrożyć na urządzeniu wyszukiwanie semantyczne, czyli technikę wyszukiwania, która znajduje odpowiednie treści na podstawie znaczenia zapytania, a nie tylko słów kluczowych. Wyszukiwanie semantyczne eliminuje konieczność ręcznego tworzenia i utrzymywania niestandardowych tabel synonimów lub taksonomii tagów. Aby wdrożyć wyszukiwanie semantyczne na urządzeniu, użyj tych bibliotek:
- pakiet SDK ML Kit, a konkretnie interfejs Embedding API, do generowania wektorów dystrybucyjnych;
- AndroidX AppSearch do przechowywania wektorów dystrybucyjnych i wyszukiwania uporządkowanych danych za pomocą danych o odległości cosinusowej, aby znajdować treści według ich semantyki.
Wymagania wstępne
Aby korzystać z wyszukiwania semantycznego na urządzeniu, musisz spełniać te wymagania dotyczące urządzenia i konta:
- Twoje urządzenie obsługuje Android AICore. Większość urządzeń obsługiwanych przez AICore korzysta z modeli przyspieszanych przez NPU, a pozostałe obsługiwane urządzenia korzystają z modelu CPU (z przyspieszeniem XNNPACK).
- Konto Google na Twoim urządzeniu jest objęte programem AICore w wersji przedpremierowej dla programistów.
- Na urządzeniu jest zainstalowana najnowsza wersja pakietu APK AICore w wersji przedpremierowej dla programistów: na urządzeniu otwórz stronę Android AICore w Sklepie Play i kliknij Aktualizuj, aby zainstalować najnowszą wersję pakietu APK. Możesz też otworzyć Ustawienia urządzenia, wyszukać Usługi systemowe lub Usługi Google, kliknąć Android AICore (beta) i kliknąć Aktualizuj, jeśli aktualizacja jest dostępna.
- Na urządzeniu jest zainstalowana najnowsza wersja aplikacji Private Compute Services: na urządzeniu otwórz stronę Private Compute Services w Sklepie Play i kliknij Aktualizuj, aby zainstalować najnowszą wersję aplikacji. Możesz też kliknąć Sklep Play > Zarządzaj aplikacjami i urządzeniami > Zarządzaj > Private Compute Services i kliknąć Aktualizuj, jeśli aktualizacja jest dostępna.
Obsługiwane modele i konfiguracja
Interfejs ML Kit Embedding API korzysta z rodziny modeli EmbeddingGemma od Google, którą zarządza Android AICore. Interfejs Embedding API używa modelu EmbeddingGemma 2 (embedding_gemma_v2) do generowania wektorów dystrybucyjnych dla danych wejściowych w postaci tekstu i obrazu.
Oto specyfikacje i limity danych wejściowych:
| Właściwość wejściowa | EmbeddingGemma 2 | Zastosowanie |
|---|---|---|
| Obsługiwane modalności | Tekst i obraz (Modality.TEXT, Modality.IMAGE) |
Aby potwierdzić, że dane wejściowe obrazu są obsługiwane, sprawdź w czasie działania, czy model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) zwraca true. |
| Limit tokenów | 256 tokenów | Sprawdź za pomocą metody model.getTokenLimit(). Każdy obraz jest kodowany na 70 tokenów, co pozostawia 186 tokenów na tekst towarzyszący. |
| Rozdzielczość obrazu | Do 768 pikseli na krótszym boku | Konstruktor klasy ImagePart akceptuje argumenty typu Bitmap, Uri lub ByteArray. Obrazy, których krótszy bok jest dłuższy niż 768 pikseli, są automatycznie zmniejszane przez pakiet SDK z zachowaniem formatu obrazu. Aby uzyskać optymalną wydajność i wykorzystanie pamięci, unikaj skrajnych formatów obrazu. |
Wymiar wektora wyjściowego to 768 (obiekt FloatArray 32-bitowych liczb zmiennoprzecinkowych). Wymiar wektora wyjściowego możesz sprawdzić w czasie działania za pomocą metody model.getMetadata()?.dimension.
Rozpocznij
Aby skonfigurować projekt do korzystania z wyszukiwania semantycznego, dodaj te zależności do pliku build.gradle.kts (lub build.gradle) na poziomie modułu.
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")
// ...
}
Zakręcony
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'
// ...
}
Aby pobrać dostępne modele, użyj metody EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()).
Definiowanie schematu AppSearch
Aby włączyć wyszukiwanie podobieństwa semantycznego, musisz zdefiniować schemat AppSearch, w którym właściwość wektora jest wyraźnie skonfigurowana pod kątem indeksowania podobieństwa.
// 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()
Indeksowanie treści dokumentu
Aby zindeksować treść dokumentu, musisz wygenerować wektor dystrybucyjny dla każdego obiektu Content, umieścić go w instancji GenericDocument i zapisać w bazie danych AppSearch.
Podczas tworzenia obiektu EmbeddingVector musisz też przekazać aktywną wartość modelSignature. AppSearch używa tego ciągu znaków modelSignature, aby zapobiegać niezgodnym zapytaniom.
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()
}
Przeprowadź wyszukiwanie semantyczne
Aby wykonać zapytanie semantyczne, przekonwertuj zapytanie (tekst lub obraz) na wektor dystrybucyjny, skonfiguruj parametry wyszukiwania wektorowego i wyślij zapytanie do bazy danych AppSearch.
Zalecenia:
- Ustaw domyślne dane na stałą
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE. - Uporządkuj wyniki według formuły
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - Użyj ciągu logicznego
"semanticSearch(getEmbeddingParameter(0), $threshold)", aby odfiltrować wyniki, w których podobieństwo osiąga wartość progową (zwykle 0,5 lub więcej).
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()
}
}
Używanie indeksu wyszukiwania hybrydowego
Aby zapewnić użytkownikom jak najlepsze wrażenia, zalecamy stosowanie strategii hybrydowej, która obejmuje zarówno wyszukiwanie semantyczne, jak i wyszukiwanie słów kluczowych. Gdy użytkownik aktywnie wpisuje tekst w pasku wyszukiwania, używaj wyszukiwania słów kluczowych, aby wyświetlać natychmiastowe sugestie autouzupełniania. Gdy użytkownik wpisze całe zapytanie, a nawet w przypadku przerw między słowami, używaj wyszukiwania semantycznego, aby odczytywać, rozumieć i pobierać najlepsze dopasowania koncepcyjne.
Ponowne indeksowanie wektorowej bazy danych
Wektory dystrybucyjne wygenerowane przez różne wersje modelu znajdują się w niezgodnych przestrzeniach wektorowych i nie można ich bezpośrednio porównywać. Za każdym razem, gdy aktualizujesz model wektora dystrybucyjnego, musisz ponownie zindeksować bazę danych AppSearch.
Zalecamy rozpoczęcie ponownego indeksowania wektorów dystryzyjnych, gdy tylko model, którego używasz, wejdzie w okres wycofywania. Aby sprawdzić harmonogram wycofywania modelu, możesz użyć interfejsu Embedding API. Wywołaj metodę model.getMetadata(), aby pobrać obiekt EmbeddingMetadata i sprawdzić te właściwości daty cyklu życia (zwracane jako obiekty LocalDate w formacie UTC):
- Wartość
deprecationDateoznacza początek okresu wycofywania modelu. Zalecamy rozpoczęcie ponownego indeksowania, gdy model nadal działa, i zakończenie go przed oficjalnym wyłączeniem modelu. - Wartość
shutdownDateto data wycofania modelu i zaprzestania jego udostępniania. Po tej dacie model nie będzie dostępny do wnioskowania, a istniejące wektory dystryzyjne tego modelu zostaną wymuszone do ponownego zindeksowania za pomocą najnowszego dostępnego modelu.
Informujemy też o nadchodzącym wycofaniu modeli, harmonogramach migracji i docelowych datach wyłączenia w informacjach o wersji ML Kit i aktualizacjach dokumentacji dla deweloperów.
Sprawdzone metody
Aby zapewnić użytkownikom dobre wrażenia, podczas integrowania wektorów dystrybucyjnych z aplikacją na Androida postępuj zgodnie z tymi sprawdzonymi metodami:
- Planowanie zadań indeksowania: zawsze planuj zadania indeksowania w tle za pomocą biblioteki WorkManager. Skonfiguruj zadania w tle tak, aby były wykonywane tylko wtedy, gdy urządzenie się ładuje i jest nieaktywne. Zestaw ML Kit nakłada limity wykorzystania modelu, podobnie jak interfejsy API LLM, przy czym limity są bardziej liberalne, gdy urządzenie się ładuje. W trybie wersji przedpremierowej dla programistów możesz pominąć limity na potrzeby testowania.
- Optymalizacja miejsca na dane i zapytań: zoptymalizuj wykorzystanie bazy danych i opóźnienia zapytań, korzystając z kwantyzacji i obcinania wymiarowości:
- Skonfiguruj schemat
AppSearchSchema, aby używać stałejQUANTIZATION_TYPE_8_BIT, jak pokazano w sekcji Definiowanie schematu AppSearch. Zmniejsza to wykorzystanie pamięci przy minimalnym wpływie na jakość, ponieważ precyzja każdej współrzędnej jest zmniejszana do 8-bitowej liczby całkowitej. - Użyj uczenia się reprezentacji Matryoshka (MRL) obsługiwanego przez model EmbeddingGemma 2, aby obciąć wektor dystrybucyjny. Więcej informacji znajdziesz w artykule o EmbeddingGemma 2 na platformie HuggingFace.
- Skonfiguruj schemat
- Spójność sygnatur: nie wysyłaj zapytań do przestrzeni wektorowej przy użyciu niedopasowanej sygnatury. Jeśli uaktualnisz wersję modelu wektorów dystrybucyjnych, musisz wyczyścić bazę danych AppSearch i ponownie wygenerować wektory dystrybucyjne dla wszystkich dokumentów przy użyciu nowej sygnatury modelu.
- Bezpieczeństwo i moderowanie treści: odpowiadasz za wdrożenie w aplikacji moderowania treści i filtrowania pod kątem bezpieczeństwa. W odróżnieniu od interfejsów API do generowania tekstu lub obrazów interfejs ML Kit Embedding API nie zawiera wbudowanych filtrów bezpieczeństwa. Ponieważ model generuje numeryczne reprezentacje wektorowe (
FloatArraywspółrzędnych) danych wejściowych podanych przez rozmówcę, a nie nowy tekst czy multimedia, dane wyjściowe modelu w postaci wektorów numerycznych nie stwarzają ryzyka związanego z bezpieczeństwem treści generatywnych. Pominięcie filtrowania bezpieczeństwa na urządzeniu pozwala też zachować niskie opóźnienia indeksowania i wydajność wyszukiwania, ponieważ pozwala uniknąć rywalizacji o zasoby procesora podczas indeksowania dokumentów o wysokiej przepustowości i zapytań w czasie rzeczywistym. Aby zwiększyć bezpieczeństwo, postępuj zgodnie z tymi wskazówkami:- Moderuj niezaufane dane wejściowe i indeksowane treści: jeśli aplikacja indeksuje lub przeszukuje niezaufane treści generowane przez użytkowników lub dane zewnętrzne, zastosuj filtry moderowania treści i zasad do tekstu lub obrazów źródłowych przed ich zindeksowaniem lub wyświetleniem wyników wyszukiwania.
- Zabezpieczanie dalszych potoków generatywnej AI: jeśli pobrane dokumenty lub fragmenty są przekazywane jako kontekst do dalszego modelu generatywnej AI, sprawdź, czy w tym modelu i jego danych wyjściowych dla użytkownika są włączone filtry bezpieczeństwa i zasady dotyczące treści.