Hướng dẫn này trình bày cách triển khai tính năng tìm kiếm ngữ nghĩa trên thiết bị, một kỹ thuật tìm kiếm giúp tìm nội dung có liên quan dựa trên ý nghĩa của cụm từ tìm kiếm, thay vì chỉ dựa vào từ khoá. Tính năng tìm kiếm ngữ nghĩa giúp bạn không cần phải tạo và duy trì bảng từ đồng nghĩa tuỳ chỉnh hoặc phân loại thẻ theo cách thủ công. Để triển khai tính năng tìm kiếm ngữ nghĩa trên thiết bị, hãy sử dụng các thư viện sau:
- SDK Bộ công cụ học máy, cụ thể là Embedding API, để tạo bản nhúng vectơ
- AndroidX AppSearch để lưu trữ các vectơ nhúng và tìm kiếm dữ liệu có cấu trúc bằng cách sử dụng chỉ số khoảng cách cosine để tìm nội dung theo ngữ nghĩa
Điều kiện tiên quyết
Để sử dụng tính năng tìm kiếm ngữ nghĩa trên thiết bị, bạn phải đáp ứng các yêu cầu sau về thiết bị và tài khoản:
- Thiết bị của bạn hỗ trợ Android AICore. Hầu hết các thiết bị được AICore hỗ trợ đều sử dụng mô hình tăng tốc NPU, trong khi các thiết bị được hỗ trợ còn lại sẽ quay về mô hình CPU (có tính năng tăng tốc XNNPACK).
- Tài khoản Google trên thiết bị của bạn đã tham gia Chương trình Bản dùng trước cho nhà phát triển AICore.
- Thiết bị của bạn có phiên bản mới nhất của APK Bản dùng trước cho nhà phát triển AICore: Trên thiết bị, hãy mở trang Cửa hàng Play của Android AICore rồi nhấn vào Cập nhật để cài đặt phiên bản mới nhất của APK. Ngoài ra, bạn có thể chuyển đến phần Cài đặt trên thiết bị, tìm Dịch vụ hệ thống hoặc Dịch vụ của Google, nhấn vào Android AICore (Thử nghiệm), rồi nhấn vào Cập nhật nếu có bản cập nhật.
- Thiết bị của bạn có phiên bản mới nhất của ứng dụng Dịch vụ điện toán riêng tư: Trên thiết bị, hãy mở trang Dịch vụ điện toán riêng tư trên Cửa hàng Play rồi nhấn vào Cập nhật để cài đặt phiên bản mới nhất của ứng dụng này. Ngoài ra, hãy chuyển đến Cửa hàng Play > Quản lý ứng dụng và thiết bị > Quản lý > Dịch vụ điện toán riêng tư rồi nhấn vào Cập nhật nếu có bản cập nhật.
Mô hình và cấu hình được hỗ trợ
API nhúng của Bộ công cụ học máy sử dụng nhóm mô hình EmbeddingGemma của Google, do Android AICore quản lý. Cụ thể, Embedding API sử dụng EmbeddingGemma 2 (embedding_gemma_v2) để tạo các mục nhúng cho dữ liệu đầu vào dạng văn bản và hình ảnh.
Sau đây là thông số kỹ thuật và giới hạn của dữ liệu đầu vào:
| Thuộc tính đầu vào | EmbeddingGemma 2 | Lưu ý về cách sử dụng |
|---|---|---|
| Các phương thức được hỗ trợ | Văn bản và hình ảnh (Modality.TEXT, Modality.IMAGE) |
Để xác nhận rằng phương thức nhập hình ảnh được hỗ trợ, hãy kiểm tra trong thời gian chạy để đảm bảo model.getMetadata()?.supportedModalities.contains(Modality.IMAGE) trả về true. |
| Giới hạn về số lượng token | 256 mã thông báo | Kiểm tra bằng phương thức model.getTokenLimit(). Mỗi hình ảnh được mã hoá thành 70 mã thông báo, còn lại 186 mã thông báo cho văn bản đi kèm. |
| Độ phân giải của hình ảnh | Tối đa 768 px ở cạnh ngắn hơn | Hàm khởi tạo cho lớp ImagePart chấp nhận các loại đối số Bitmap, Uri hoặc ByteArray. Hình ảnh có kích thước ngắn hơn 768 px sẽ tự động được SDK giảm tỷ lệ trong khi vẫn giữ nguyên tỷ lệ khung hình. Để đạt được hiệu suất và mức sử dụng bộ nhớ tối ưu, hãy tránh sử dụng tỷ lệ khung hình quá lớn hoặc quá nhỏ. |
Kích thước vectơ đầu ra là 768 (một đối tượng FloatArray gồm các số thực 32 bit). Bạn có thể kiểm tra kích thước vectơ đầu ra trong thời gian chạy bằng phương thức model.getMetadata()?.dimension.
Bắt đầu
Để định cấu hình dự án sử dụng tính năng tìm kiếm ngữ nghĩa, hãy thêm các phần phụ thuộc sau vào tệp build.gradle.kts (hoặc build.gradle) cấp mô-đun.
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'
// ...
}
Để truy xuất các mô hình có sẵn, hãy sử dụng phương thức EmbeddingGeneration.getEmbeddingModels(EmbeddingOptions()).
Xác định giản đồ AppSearch
Để bật tính năng tìm kiếm theo độ tương đồng về ngữ nghĩa, bạn phải xác định một giản đồ AppSearch, trong đó thuộc tính vectơ được định cấu hình rõ ràng để lập chỉ mục độ tương đồng.
// 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()
Lập chỉ mục nội dung tài liệu
Để lập chỉ mục nội dung tài liệu, bạn phải tạo một vectơ nhúng cho mỗi đối tượng Content, gói đối tượng đó trong một thực thể GenericDocument rồi lưu vào cơ sở dữ liệu AppSearch.
Khi tạo đối tượng EmbeddingVector, bạn cũng phải truyền giá trị modelSignature đang hoạt động. AppSearch sử dụng chuỗi modelSignature này để ngăn các cụm từ tìm kiếm không tương thích.
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()
}
Thực hiện tìm kiếm ngữ nghĩa
Để thực hiện truy vấn ngữ nghĩa, hãy chuyển đổi cụm từ tìm kiếm (văn bản và/hoặc hình ảnh) thành vectơ nhúng, định cấu hình các tham số tìm kiếm vectơ và truy vấn cơ sở dữ liệu AppSearch.
Đề xuất:
- Đặt chỉ số mặc định thành hằng số
SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE. - Xếp hạng kết quả theo công thức
"sum(this.matchedSemanticScores(getEmbeddingParameter(0)))". - Sử dụng chuỗi logic
"semanticSearch(getEmbeddingParameter(0), $threshold)"để lọc kết quả có độ tương đồng đáp ứng giá trị ngưỡng của bạn (thường là 0,5 trở lên).
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()
}
}
Sử dụng chỉ mục của Tìm kiếm kết hợp
Để mang lại trải nghiệm tốt nhất cho người dùng, bạn nên sử dụng chiến lược kết hợp cả tính năng tìm kiếm theo ngữ nghĩa và tìm kiếm theo từ khoá. Khi người dùng đang nhập nội dung vào thanh tìm kiếm, hãy sử dụng tính năng tìm kiếm từ khoá để hiển thị các đề xuất nhập trước tức thì; khi họ nhập cụm từ tìm kiếm hoàn chỉnh hoặc thậm chí là khi ngắt từ, hãy sử dụng tính năng tìm kiếm ngữ nghĩa để đọc, hiểu và tìm nạp các kết quả phù hợp nhất về mặt khái niệm.
Lập chỉ mục lại cơ sở dữ liệu vectơ
Các mục nhúng do các phiên bản mô hình khác nhau tạo ra nằm trong không gian vectơ không tương thích và không thể so sánh trực tiếp. Bạn phải lập chỉ mục lại cơ sở dữ liệu AppSearch mỗi khi cập nhật mô hình nhúng.
Bạn nên bắt đầu lập chỉ mục lại các mục nhúng ngay khi mô hình bạn đang sử dụng bước vào giai đoạn ngừng hoạt động. Bạn có thể sử dụng Embedding API để kiểm tra lịch ngừng hoạt động của một mô hình. Gọi phương thức model.getMetadata() để truy xuất đối tượng EmbeddingMetadata và kiểm tra các thuộc tính ngày trong vòng đời này (được trả về dưới dạng đối tượng LocalDate theo giờ UTC):
- Giá trị
deprecationDateđánh dấu thời điểm bắt đầu giai đoạn ngừng sử dụng mô hình. Bạn nên bắt đầu lập chỉ mục lại trong khi mô hình tiếp tục hoạt động và hoàn tất việc lập chỉ mục lại trước khi mô hình chính thức ngừng hoạt động. - Giá trị
shutdownDatelà thời điểm mô hình sẽ ngừng hoạt động và không còn được phân phát. Sau ngày này, bạn sẽ không thể truy cập vào mô hình để suy luận và các mục nhúng hiện có của mô hình này sẽ buộc phải lập chỉ mục lại bằng mô hình mới nhất hiện có.
Chúng tôi cũng thông báo về việc ngừng sử dụng mô hình sắp tới, tiến trình di chuyển và ngày ngừng hoạt động dự kiến thông qua ghi chú phát hành của Bộ công cụ học máy và nội dung cập nhật tài liệu dành cho nhà phát triển.
Các phương pháp hay nhất
Để mang lại trải nghiệm tốt cho người dùng, hãy làm theo các phương pháp sau khi tích hợp các mục nhúng vectơ vào ứng dụng Android:
- Lên lịch cho khối lượng công việc lập chỉ mục: Luôn lên lịch cho khối lượng công việc lập chỉ mục trong nền bằng thư viện WorkManager. Định cấu hình các tác vụ trong nền để chỉ chạy khi thiết bị đang sạc và ở trạng thái rảnh. Bộ công cụ học máy áp đặt hạn mức sử dụng mô hình, tương tự như API LLM, với hạn mức cao hơn khi thiết bị đang sạc. Ở chế độ Bản dùng trước cho nhà phát triển, bạn có thể bỏ qua các giới hạn về hạn mức để kiểm thử.
- Tối ưu hoá bộ nhớ và truy vấn: Tối ưu hoá mức sử dụng cơ sở dữ liệu và độ trễ truy vấn bằng cách sử dụng tính năng lượng tử hoá và cắt bớt chiều:
- Định cấu hình giản đồ
AppSearchSchemađể sử dụng hằng sốQUANTIZATION_TYPE_8_BIT, như trong phần Xác định giản đồ AppSearch. Việc này giúp giảm mức sử dụng bộ nhớ nhưng chỉ tác động tối thiểu đến chất lượng bằng cách thu hẹp độ chính xác của mỗi toạ độ thành một số nguyên 8 bit. - Sử dụng phương pháp Học biểu diễn Matryoshka (MRL), được mô hình EmbeddingGemma 2 hỗ trợ, để cắt bớt vectơ nhúng. Để tìm hiểu thêm, hãy xem EmbeddingGemma 2 trên HuggingFace.
- Định cấu hình giản đồ
- Tính nhất quán của chữ ký: Đừng truy vấn không gian vectơ bằng chữ ký không khớp. Nếu nâng cấp phiên bản mô hình nhúng, bạn phải xoá cơ sở dữ liệu AppSearch và tạo lại các mục nhúng cho tất cả tài liệu bằng chữ ký mô hình mới.
- An toàn và kiểm duyệt nội dung: Bạn chịu trách nhiệm triển khai quy trình kiểm duyệt nội dung và lọc an toàn trong ứng dụng của mình. Không giống như các API văn bản hoặc hình ảnh tạo sinh, API nhúng của Bộ công cụ học máy không có bộ lọc an toàn tích hợp. Vì đầu ra của mô hình là các bản biểu diễn vectơ số (toạ độ
FloatArray) của thông tin đầu vào do phương thức gọi cung cấp thay vì tạo văn bản hoặc nội dung nghe nhìn mới, nên các vectơ số vốn không gây ra rủi ro về an toàn nội dung tạo sinh. Việc bỏ qua tính năng lọc an toàn trên thiết bị cũng giúp duy trì hiệu suất tìm kiếm và lập chỉ mục có độ trễ thấp bằng cách tránh tranh chấp CPU trong quá trình lập chỉ mục tài liệu có thông lượng cao và truy vấn theo thời gian thực. Để cải thiện mức độ an toàn, hãy làm theo các nguyên tắc sau:- Kiểm duyệt nội dung đầu vào không đáng tin cậy và nội dung được lập chỉ mục: Nếu ứng dụng của bạn lập chỉ mục hoặc tìm kiếm nội dung do người dùng tạo không đáng tin cậy hoặc dữ liệu bên ngoài, hãy áp dụng bộ lọc chính sách và kiểm duyệt nội dung cho văn bản hoặc hình ảnh nguồn trước khi lập chỉ mục hoặc hiển thị kết quả tìm kiếm.
- Bảo vệ quy trình AI tạo sinh hạ nguồn: Nếu tài liệu hoặc đoạn văn bản được truy xuất được truyền dưới dạng ngữ cảnh cho mô hình AI tạo sinh hạ nguồn, hãy xác minh rằng các bộ lọc an toàn và chính sách nội dung đã được bật trên mô hình hạ nguồn đó và kết quả đầu ra dành cho người dùng.