Dalam sesi ini, kita akan mempelajari berbagai kasus penggunaan GenAI menggunakan API ML Kit untuk memahami kapan GenAI di perangkat ideal untuk aplikasi dan juga memahami cara mengevaluasi kasus penggunaan GenAI yang ada untuk melihat apakah model di perangkat dapat memenuhi standar kualitasnya.
Transkrip
David Chou
Hei.
Selamat siang, semuanya.
Nama saya David Chou.
Saya adalah Product Lead di Android AICore.
Saya sangat senang membahas kemampuan AI di perangkat kami bersama Anda.
Hari ini, kita akan membahas beberapa area inti.
Pertama, pikirkan alasan Anda harus menggunakan AI di perangkat, terutama mengingat percepatan kemajuan AI di cloud.
Kedua, memikirkan beberapa peningkatan yang telah kami lakukan selama setahun terakhir untuk membuat model AI di perangkat ini sangat efektif.
Dan hal terakhir yang akan kita bahas adalah contoh menyeluruh yang menunjukkan cara Anda dapat memanfaatkan semua manfaat ini menggunakan AI di perangkat.
Jadi, pertama-tama, mengapa menggunakan AI di perangkat?
Ada 4 alasan utama.
Pertama adalah kecepatan, kedua adalah biaya, ketiga adalah ketersediaan, dan keempat adalah privasi.
Jadi, dalam hal kecepatan, Anda dapat melihat bahwa inferensi AI di perangkat kini mulai mencapai titik di mana inferensi tersebut dapat lebih cepat daripada berjalan di cloud hanya karena Anda tidak perlu meneruskan informasi bolak-balik antara perangkat pengguna dan di cloud.
Jadi, dalam banyak kasus penggunaan yang telah kami uji, kami melihat peningkatan kecepatan 2x lipat saat membandingkan AI di perangkat dengan cloud.
Yang kedua adalah memikirkan biaya.
Jadi, bayangkan Anda menjalankan sesuatu dengan satu juta inferensi per hari.
Anda sebenarnya dapat menghemat ratusan ribu dolar jika menjalankan AI di perangkat, khususnya dari perangkat pengguna, dibandingkan dengan menjalankannya di cloud.
Dan yang ketiga adalah ketersediaan offline.
Jadi, kami mengirimkan survei awal tahun ini, dan 62% developer yang kami ajak bicara mengatakan bahwa ketersediaan offline adalah salah satu dari tiga alasan utama untuk menggunakan AI di perangkat.
Jadi,baik pengguna Anda berada di ketinggian 35.000 kaki di udara atau di lantai bawah tanah stasiun Tokyo dan tidak memiliki akses ke internet, mereka tetap dapat memanfaatkan semua kemampuan ini.
Terakhir, privasi adalah salah satu alasan terbesar yang kami temukan saat menyurvei developer.
Jadi, 72% responden mengurutkannya sebagai tiga alasan teratas.
40% Menempatkannya di peringkat pertama.
Jadi, terkait pesan, catatan suara, foto, atau bahkan informasi keuangan atau medis, pengguna ingin menjaga privasinya, sehingga menjalankan inferensi di perangkat akan sangat berharga bagi mereka.
Jadi, dengan mempertimbangkan semua manfaat ini, kami membangun ML Kit agar Anda dapat mempertahankan dan memanfaatkan semua kemampuan ini.
Kami mencoba membuat proses ini sangat sederhana.
Jadi, jika ada keluhan, jangan ragu untuk menghubungi saya.
Saya akan memberikan info kontak saya di akhir presentasi ini.
Kami ingin mencari tahu cara mengaktifkan kasus penggunaan Anda.
Jadi, untuk memulai, kami telah mengaktifkan banyak kemampuan untuk kasus penggunaan seperti ucapan dan kreatif.
Jadi, seperti pemeriksaan tata bahasa, penulisan ulang, dan juga mengaktifkan perintah multimodal yang fleksibel.
Jadi, tujuan kami adalah mencoba membuat fitur ini semudah mungkin digunakan.
Inilah alasan banyak pengguna dan developer kami mulai mengadopsinya.
Saat ini, kami sudah memiliki 9 aplikasi pihak ketiga yang memiliki 10 juta download yang menggunakan fitur GenAI ini dalam produksi.
Kami juga melihat peningkatan skala yang jauh lebih besar di aplikasi pihak pertama dan pihak ketiga, serta pertumbuhan QoQ sebesar 110% dalam peluncuran aplikasi untuk fitur produksi AI.
Jadi, di seluruh pesan, materi iklan, dan keuangan, kami mendorong Anda untuk mencoba API yang kami sediakan.
Kami rasa kemungkinannya tidak terbatas, dan Anda mungkin memiliki imajinasi yang lebih baik daripada kami dalam banyak kasus penggunaan ini, jadi kami ingin bermitra dan mencari tahu bagaimana kita dapat bekerja sama.
Jadi, sekarang Anda memahami manfaat tingkat tinggi.
Jadi, Anda mungkin bertanya-tanya, oke, seperti apa jangkauannya?
Bagaimana performanya di perangkat?
Saat kita berbicara tentang kecepatan, seberapa bagus sebenarnya?
Atau seperti kualitas model itu sendiri, bukan?
Jadi, kita akan membahasnya satu per satu.
Dalam hal jangkauan, kami sudah tersedia di 175 juta perangkat AICore saat ini.
Kami berharap fitur ini akan diperluas secara signifikan pada tahun mendatang, sehingga dapat digunakan di ratusan juta perangkat lainnya.
Hal ini dapat dilakukan salah satunya karena kami telah membuat harness yang sangat sederhana.
Jadi, Anda hanya perlu mengintegrasikan API ML Kit.
Ada banyak kompleksitas yang disertakan dalam AICore untuk mengelola sinyal keamanan input dan output, mengelola download model beserta bobotnya, dan memikirkan cara menyiapkannya dalam runtime.
Jadi, tujuannya adalah membuat ML Kit semudah mungkin digunakan sehingga Anda hanya perlu memikirkan cara memberikan pengalaman yang luar biasa, dan kami akan mengurus sisanya.
David Chou
Kami juga dengan senang hati mengumumkan bahwa kami telah mengurangi RAM yang diperlukan untuk menjalankan Gemini Nano secara signifikan, yang merupakan model utama yang mendukung banyak API GenAI ini.
Jadi, kami telah mengurangi penggunaan RAM dari 3,6 GB menjadi 1,1 GB.
Jadi, ada 4 peningkatan inti yang telah kami lakukan untuk mewujudkannya.
Pertama, menggunakan kuantisasi 2-bit, bukan 4-bit.
Jadi, setengah bit berarti setengah byte yang harus Anda salurkan melalui model.
Cara kedua adalah menggunakan KV cache bersama.
Jadi, kami telah mengurangi ukuran ini secara signifikan dengan mengizinkan 20 lapisan pertama model untuk membagikannya dalam paket 35 MB, yang berarti Anda masih dapat mengakses ukuran konteks token 16K dalam ukuran paket yang sangat kecil ini.
Ketiga, mengaktifkan encoder audio dan visi lambat.
Jadi, untuk banyak kasus penggunaan teks yang kita lihat, Anda sebenarnya tidak perlu memuatnya sejak awal.
Jadi, Anda akan menghemat setengah gigabyte RAM lagi.
Terakhir, untuk meningkatkan masa pakai baterai, kami juga telah mengaktifkan penskalaan clock NPU.
Artinya, NPU dapat berjalan secepat yang diperlukan untuk menghemat daya sekaligus memastikan kualitas tinggi tetap diberikan pada kasus penggunaan Anda.
Ada beberapa peningkatan lain yang telah kami lakukan terkait percepatan.
Jadi, yang pertama adalah mengaktifkan prefix caching.
Anda dapat melihat bahwa dalam contoh ini, perintah sistem digunakan kembali untuk beberapa giliran, bukan?
Oleh karena itu, pemrosesan ini menjadi sangat mahal.
Namun, dengan prefix caching, kita pada dasarnya mengatakan bahwa Anda tidak perlu memproses ulang perintah sistem tersebut berulang kali, bukan?
Anda dapat menyimpannya di cache awalan, yang berarti untuk contoh ini, Anda menghemat 60% token yang harus Anda teruskan, yang juga berarti peningkatan kecepatan sebesar 60% di perangkat apa pun yang Anda gunakan.
Peningkatan kedua yang kami lakukan adalah dengan dekode spekulatif.
Jadi, dalam contoh ini, Anda dapat melihat bahwa Gemini Nano menghasilkan satu token per penerusan.
Hal ini karena LLM bersifat autoregresif, yang berarti LLM harus terus melihat token sebelumnya yang dihasilkan untuk menentukan token berikutnya.
Jadi, cara kami menangani hal ini adalah dengan membangun model penyusun draf yang menghasilkan banyak token dengan kecepatan yang jauh lebih tinggi.
Model drafter ini jauh lebih kecil daripada Gemini Nano, sehingga dapat menghasilkan banyak token sekaligus, lalu meneruskannya ke Gemini untuk melihat token mana yang benar-benar sesuai dengan tebakan Gemini Nano.
Jadi, dalam contoh ini, Anda dapat melihat, pada penerusan pertama, model menghasilkan enam token, dan penerusan berikutnya menghasilkan lima token, lalu, pada giliran ketiga, model sebenarnya sudah selesai.
Jadi, yang kami lihat adalah untuk banyak kasus penggunaan internal yang kami amati, hal ini menghasilkan peningkatan kecepatan rata-rata 2x lipat.
Anda mungkin mendapatkan peningkatan yang lebih cepat, bergantung pada hardware Anda.
Oke.
Jadi, inilah alasan, Anda tahu, kembali ke kecepatan, Anda sebenarnya dapat melihat peningkatan kecepatan yang dramatis, bukan, antara di perangkat dan cloud bahkan di jaringan 5G.
Anda dapat melihat bahwa sekadar mengupload data tersebut ke cloud akan memerlukan waktu sekitar satu detik, bukan, untuk 1.000 hingga 1.300 token input, dan meskipun prefill dan dekode dalam perjalanan cloud mungkin lebih cepat, perjalanan menyeluruh masih jauh lebih cepat saat Anda menjalankannya di perangkat.
Kemudian, terkait kualitas, kami dengan senang hati mengumumkan bahwa model 2B ini sebenarnya memiliki performa yang lebih baik dibandingkan model 27B tahun lalu.
Meskipun performanya tidak sebaik model termutakhir yang kita miliki saat ini, yang kami tunjukkan di sini adalah bahwa ada kemajuan yang signifikan dalam hal AI pada model di perangkat ini, bukan?
Jadi, Anda dapat melihat bahwa untuk MMLU-Pro, yang merupakan tolok ukur pengetahuan standar, model 2B hampir sama baiknya dengan model parameter 27B dari tahun lalu.
Lalu, dalam hal tolok ukur sains tingkat pascasarjana, penalaran, dan penggunaan alat agentic, model 2B ini sebenarnya lebih baik, meskipun ukurannya kurang dari sepersepuluh ukuran model tahun lalu.
Oleh karena itu, kami memperkirakan tren ini akan terus berlanjut.
Kami terus bekerja sama dengan tim GDM untuk memastikan bahwa kami dapat terus melakukan peningkatan ini pada model sehingga Anda bisa mendapatkan manfaat terbaik dari model ini.
David Chou
Oke.
Jadi, saya baru saja menyampaikan banyak manfaat dan peningkatan baru kepada Anda, dan mungkin Anda bertanya, "Apa yang sebenarnya dapat kita lakukan dengan model ini?"
Jadi, ada semua peningkatan kemampuan yang juga telah kami lakukan di API.
Jadi, dengan petunjuk sistem, Anda dapat menetapkan persona.
Dengan input multi-gambar, Anda dapat mengupload banyak gambar sekaligus.
Kami juga telah mengaktifkan mode berpikir awal tahun ini, sehingga Anda dapat menghabiskan lebih banyak waktu untuk masalah penalaran serta panggilan alat.
Terakhir, dengan output terstruktur, kami juga memungkinkan Anda menganotasi class Kotlin dan mendapatkan kembali objek yang sesuai dengan skema spesifik Anda.
Selain itu, kami juga mencoba meminta developer untuk memberikan peningkatan baru yang ingin mereka lakukan.
Jadi, 91% developer yang kami ajak bicara saat disurvei mengatakan bahwa mereka akan meluncurkan pengalaman jika salah satu kemampuan ini tersedia.
Jadi, awal minggu ini, kami meluncurkan empat kemampuan ini: terjemahan ucapan, embedding, text-to-speech, dan pemanggilan fungsi.
Dengan terjemahan ucapan, Anda dapat mentranskripsikan audio dari lebih dari 20 bahasa.
Dengan embedding, Anda dapat menelusuri konten aplikasi berdasarkan makna semantik, bukan hanya menggunakan penelusuran kata kunci.
Dengan text-to-speech, Anda kini dapat memberikan suara mirip manusia untuk pengalaman Anda, sehingga Anda tidak perlu lagi menggunakan suara robot.
Terakhir, dengan pemanggilan fungsi, Anda dapat mengizinkan aplikasi mengambil tindakan atas nama pengguna.
Jadi, kami telah berpartner dengan beberapa developer untuk melihat cara mereka dapat membangun agen dalam aplikasi dalam pengalaman mereka, dan kami berharap dapat meluncurkannya tahun depan.
David Chou
Dengan semua kemampuan ini, mari kita lihat contoh menyeluruh saat kita membangun dengan semua kemampuan ini.
Jadi, bayangkan ada seorang fotografer Amerika yang sedang bepergian di Jepang.
Dan, seperti yang Anda lihat dari foto, mereka jelas tidak memiliki cukup kamera atau lensa.
Jadi, mereka memesan lensa baru dan mengirimkannya ke hotel.
Namun, ada masalah: Saat menjalani hari, mereka menerima pesan suara dan diberi tahu bahwa mereka harus membayar biaya bea cukai sebesar 6.800 yen agar dapat menerima paket pada akhir hari.
Ia juga menerima pesan teks yang berisi foto paket yang ditahan.
Jadi, dalam contoh ini, pengguna tertarik untuk memiliki semacam perlindungan deteksi scam, bukan?
Mereka ingin memahami cara mengidentifikasi pesan suara sebagai pesan suara yang sah atau penipuan.
Jadi, kami ingin menjaga kerahasiaan semua informasi, kami ingin memastikan bahwa informasi tersebut didasarkan pada fakta, tetapi juga dapat dijelaskan.
Jadi, kita akan memastikan bahwa mereka dapat memahami teks bahasa Jepang, dan juga, dapat menggunakan kemampuan bahasa apa pun yang tersedia jika mereka memiliki kebutuhan aksesibilitas.
Jadi, akan ada empat langkah di sini, bukan?
Langkah pertama adalah memproses pesan suara tersebut menjadi teks, sehingga pesan suara tersebut akan diterjemahkan.
Kita akan menggunakan Speech Translation API untuk melakukannya.
Kemudian, mereka akan dapat mengajukan pertanyaan jika mau, jadi kita akan menggunakan Speech Recognition API.
Kemudian, kita ingin menentukan fungsi yang dapat mengambil informasi yang diperlukan pengguna untuk mendapatkan hal yang mereka butuhkan.
Jadi, kita akan menggunakan Prompt API dan panggilan fungsi.
Sekarang, tindakan ini akan berjalan di latar belakang tanpa terlihat oleh pengguna.
Dan ketiga, kita akan mencari tahu cara menyimpan dan mengambil informasi tersebut menggunakan embedding API dan AppSearch.
Tindakan ini juga akan berjalan di latar belakang.
Kemudian, terakhir, kita akan membuat keputusan berdasarkan informasi yang kita kumpulkan, menggunakan Prompt API, output terstruktur, dan sintesis ucapan.
Oke.
Jadi, mari kita mulai.
Kita akan menggunakan terjemahan ucapan dan pengenalan ucapan.
Jadi, langkah pertamanya adalah mengambil pesan suara dan teks tersebut, lalu mengubahnya menjadi informasi yang berguna, bukan?
Jadi, pengguna menerima pesan ini, "Hei, Anda harus membayar biaya sebesar 6.800 yen ini.
Bea masuk, mungkin belum dibayar di muka, jadi Anda harus membayarnya."
Jadi, mereka ingin dapat memiliki terjemahan itu, bukan?
Jadi, salah satu hal yang ingin kami tunjukkan di sini adalah terjemahan dan pertanyaan pengguna tentang informasi tersebut semuanya dikirim ke Gemini Nano 4 menggunakan encoder audio yang sama, karena encoder audio telah dilatih dalam lebih dari 100 bahasa.
Jadi, Anda bisa mendapatkan terjemahan tersebut, serta ASR untuk teks pertanyaan pengguna.
Jadi, untuk terjemahan, kita hanya perlu menyiapkan bahasa sumber dan tujuan.
val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)Dalam hal ini, kita akan menggunakan bahasa Jepang serta bahasa Inggris Amerika.
Kita akan menyiapkan flow Kotlin yang meneruskan byte secara langsung ke penerjemah ucapan.
val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)
val audioFlow = flow {
emit(TranslationAudio(Locale.JAPANESE, courierVoicemailBytes))
}
val request = SpeechTranslatorRequest.Builder(audioFlow).build()
translator.startTranslation(request).collect { response ->
if (response is SpeechTranslatorResponse.Text) {
translatedCourierClaim = response.translatedText
}
}Kemudian, kita dapat mengumpulkan respons menggunakan teks yang diterjemahkan.
Oke.
Bagus.
Jadi, sekarang kita mengerti apa yang sebenarnya dikatakan pesan suara, bukan?
Ada paket lensa kamera.
Paket ditahan di Bea Cukai.
Mereka harus membayar biaya sebesar 6.800 yen.
Sekarang, pengguna ingin dapat mengajukan pertanyaan tentang hal itu.
Oke.
Jadi, apakah penahanan bea cukai ini benar-benar nyata?
Dalam hal ini, kita akan menggunakan ASR API.
val recognizer = SpeechRecognition.getClient(
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
)Yang kita lakukan masih menetapkan bahasa Inggris Amerika sebagai lokalitas.
Kemudian, kita dapat menyetelnya ke mode lanjutan, yang memanfaatkan encoder audio.
Kemudian, kita dapat meneruskan informasi tersebut, mengumpulkan respons akhir, lalu mengubahnya menjadi informasi yang akan kita teruskan ke Gemini Nano.
val recognizer = SpeechRecognition.getClient(
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
)
val request = speechRecognizerRequest {
audioSource = AudioSource.fromMic()
recognitionOptions { enableWordTiming = true } // optional
}
recognizer.startRecognition(request).collect { response ->
when (response) {
is SpeechRecognizerResponse.PartialTextResponse ->
liveCaption = response.text
is SpeechRecognizerResponse.FinalTextResponse ->
planAndCallVaultTool(translatedCourierClaim, response.text)
}
}Oke.
Jadi, sekarang kita telah menyelesaikan bagian pertama dari asupan.
Seperti yang Anda lihat, tidak diperlukan banyak baris kode.
David Chou
Jadi, bagian kedua adalah triase, yaitu memikirkan cara memanfaatkan Prompt API dan panggilan fungsi untuk mencari tahu jenis informasi yang diperlukan.
Oke.
Jadi, untuk membangun arsitektur ini, kita akan menyiapkan beberapa petunjuk sistem terlebih dahulu.
Kemudian, kita juga akan meneruskan pesan suara, teks, dan foto, serta pertanyaan pengguna, sebagai input ke Gemini Nano.
Kemudian, kita akan menentukan fungsi yang benar-benar mengambil informasi yang kita perlukan.
retrieveOrderAndDeliveryPhotos(
searchQuery = "camera lens customs",
claimedFeeUsd = 45,
comparePackagePhotos = true
)Jadi, untuk memulai, kita akan menyiapkan petunjuk sistem.
// Keep SystemInstruction static across Turn 1 & Turn 2:
val securitySystemInstruction = SystemInstruction(
"""
You are an on-device parcel & phishing security auditor.
1. Cross-check packaging, carrier labels and tracking across all images.
2. Verify customs fee claims against local DDP (Delivered Duty Paid) order records.
3. Flag any payment demand when the order record shows duties prepaid.
""".trimIndent()
)Di sini, Anda dapat melihat contoh yang sangat sederhana tentang tampilannya, untuk menyiapkan auditor keamanan, yang menyatakan bahwa kita akan memeriksa silang kemasan dan label, melihat biaya bea cukai, serta menandai permintaan pembayaran yang diperlukan.
Anda mungkin akan mendapati bahwa pengoptimalan perintah akan sangat membantu dalam memberikan kualitas yang jauh lebih baik.
Setelah memiliki kumpulan petunjuk sistem tersebut, kita dapat mengambil semua konten yang kita miliki dari langkah sebelumnya, terjemahan pesan suara dan teks, serta pertanyaan pengguna, lalu meneruskan semuanya ke kolom TextPart ini, bersama dengan petunjuk sistem untuk mendapatkan informasi tersebut.
// Pull in the Speech Translation (AST) & Speech Recognition (ASR) outputs:
val turn1Prompt = """
Translated voicemail: $translatedCourierClaim
Incoming text message: $callerText
User's spoken question: $userQuestion
""".trimIndent()
val turn1Request = generateContentRequest(
securitySystemInstruction,
TextPart(turn1Prompt)
) { /* enableThinking & setTools */ }Lalu, kita ingin menentukan fungsi yang dapat kita gunakan.
Jadi, untuk contoh khusus ini, kami dengan senang hati mengumumkan bahwa kami telah mengintegrasikannya dengan fungsi aplikasi.
Jadi, jika Anda sudah familier dengan hal itu, Anda mungkin sudah memahami cara kerjanya secara garis besar.
Jadi, di sini kita menyiapkan titik entri AppFunctionService, lalu menentukan fungsi aplikasi.
// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {
}Anda dapat melihat bahwa prosesnya sama persis dengan saat Anda menyiapkan fungsi aplikasi untuk agen sistem.
Anda menetapkan parameter searchQuery, biaya klaim, dan juga apakah kita harus membandingkan foto paket atau tidak, sebagai beberapa contoh parameter, bukan?
// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {
/**
* Searches local orders and order images in AppSearch.
* @param searchQuery Semantic query to match the order
* @param claimedFeeUsd Customs fee claimed in USD
* @param comparePackagePhotos Whether to fetch order images to compare
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun retrieveOrderAndDeliveryPhotos(
searchQuery: String,
claimedFeeUsd: Int,
comparePackagePhotos: Boolean
): VaultMatch = withContext(Dispatchers.IO) { executeVaultSearch(searchQuery) }
}Anda dapat menentukan yang berbeda.
Terakhir, untuk fungsi tersebut, kita memiliki fungsi executeVaultSearch yang akan kita tentukan nanti.
Satu hal yang ingin kami sampaikan di sini adalah bahwa kami menyediakan mode penalaran.
val vaultTools = AppFunctionTools.getClient().getAppFunctions()
val turn1Request = generateContentRequest(securitySystemInstruction, TextPart(turn1Prompt)) {
enableThinking = true
setTools(vaultTools)
}
val call = generativeModel.generateContent(turn1Request)
.candidates.first().functionCalls.first()Sebaiknya gunakan parameter ini saat menjalankan panggilan alat.
Alasannya adalah karena kami telah melakukan banyak pengujian yang menunjukkan bahwa ada peningkatan akurasi sebesar 13 poin dari 100 saat menjalankan panggilan alat di Gemini Nano 4.
Jadi, bahkan dalam beberapa kasus, jumlahnya jauh lebih tinggi, dan ini mungkin memberi Anda hasil yang jauh lebih baik.
Dan jika Anda ingin mempelajari lebih lanjut tentang panggilan fungsi, silakan hadiri sesi rekan saya, Jolanda dan Victoria, yang akan berlangsung nanti.
Ini adalah kemampuan agentic di aplikasi Anda.
Sepertinya Victoria ada di antara penonton, jadi silakan menyapanya juga.
David Chou
Oke.
Jadi, sekarang kita telah menyelesaikan dua langkah pertama.
Mari kita bahas cara kerja pengambilan.
Jadi, kita akan menggunakan embedding dan AppSearch.
Jadi, mungkin mundur beberapa langkah, ya, dalam perjalanan pengguna, pada saat mereka memesan lensa, yang ingin kita lakukan adalah mengindeks informasi tersebut sehingga dapat diakses oleh pengguna dan juga developer, bukan?
Jadi, di sini kita memiliki detail informasi tentang nomor paket pesanan.
Paket ini juga memiliki nomor pelacakan.
Email ini berisi beberapa detail tentang tujuan pengirimannya.
Dan Anda dapat melihat bahwa di sini sudah disebutkan bahwa bea masuk telah dibayar di muka.
Jadi, yang akan kita lakukan adalah membuat embedding multimodal untuk mengindeks informasi tersebut bersama dengan informasi lain yang mungkin penting bagi kita.
Jadi, berikut tampilannya.
val embedModel = EmbeddingGeneration.getEmbeddingModels().first()
val docVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(orderEmailLensBmp), // the lens photo in the email
TextPart(orderEmailBody) // the order details
))
)
).embedding.vectorKami mengambil email pesanan.
Sekali lagi, sangat sederhana, kita hanya meneruskan satu gambar dan satu teks, tetapi Anda mungkin memiliki lebih banyak konten yang ingin diindeks di sini.
Kemudian, kita akan membuat vektor embedding.
Jadi, setelah itu, kita akan menggunakan AppSearch untuk membuat dokumen, lalu menetapkan properti apa pun yang menurut kita diperlukan untuk membuat dokumen tertentu tersebut.
val embedModel = EmbeddingGeneration.getEmbeddingModels().first()
val docVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(orderEmailLensBmp), // the lens photo in the email
TextPart(orderEmailBody) // the order details
))
)
).embedding.vector
val doc = GenericDocument.Builder<GenericDocument.Builder<*>>(
"orders", "order_jp_904", "OrderRecord"
)
.setPropertyString("summary", "Shipped DDP, duties prepaid ($0 owed). In transit.")
.setPropertyString("photoUris", "order_email_lens.png")
.setPropertyEmbedding(
"embedding",
EmbeddingVector(docVector, embedModel.getModelSignature())
).build()Lalu, satu hal yang perlu diperhatikan di sini adalah alasan kita ingin melakukan embedding multimodal, bukan, karena mungkin tidak ada kata yang sama di sini, tetapi karena gambar dan teksnya memiliki konten yang serupa, hal ini membantu kita mendapatkan kesamaan semantik.
Jadi, kita dapat menggunakan kesamaan kosinus untuk menemukan item yang tidak memiliki kata yang sama.
Oke, jadi ini adalah fungsi executeVaultSearch yang telah kita bahas sebelumnya.
suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
}Inilah yang dipanggil oleh fungsi aplikasi yang telah kita tentukan.
Jadi, dalam contoh ini, kita meneruskan kueri penelusuran yang telah dibuat oleh Gemini Nano.
Kemudian, kita akan menggunakan strategi peringkat, kesamaan kosinus.
suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
val spec = SearchSpec.Builder()
.addEmbeddingParameters(EmbeddingVector(qVec, embedModel.getModelSignature()))
.setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true).build()
}Lalu, this.matchedSemanticScores membantu kita mendapatkan informasi tersebut.
Lalu, terakhir, kita akan menentukan nilai minimum 0,68 untuk menentukan kesamaan kosinus minimum agar kita dapat mengambil informasi.
suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
val spec = SearchSpec.Builder()
.addEmbeddingParameters(EmbeddingVector(qVec, embedModel.getModelSignature()))
.setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true).build()
val results = appSearchSession.search(
"semanticSearch(getEmbeddingParameter(0), 0.68)", spec
)
// Cleanly close the cursor.
return results.use { it.nextPageAsync.await().first().genericDocument.toVaultMatch() }
}Jadi, ini adalah sesuatu yang mungkin ingin Anda coba saat menjalankan kasus penggunaan Anda sendiri, hanya untuk melihat apa yang benar-benar berhasil untuk Anda.
Lalu, satu hal yang juga ingin kami sampaikan di sini adalah, jika Anda memikirkan keamanan model ini, Gemini Nano sebenarnya bukanlah yang menjalankan kode apa pun, bukan?
Jadi, Gemini Nano hanya mengajukan pertanyaan, tetapi aplikasi Anda menentukan kodenya, dan kemudian aplikasi itulah yang benar-benar menjalankan kode itu sendiri, mengambil informasi tersebut, lalu menentukan apa yang akan diteruskan ke Gemini Nano.
Jadi, hal ini memberi Anda lebih banyak kontrol atas apa yang ingin Anda ekspos ke Gemini Nano dan juga membantu memastikan privasi dan keamanan yang lebih baik bagi pengguna Anda.
David Chou
Oke, dan sekarang ini adalah langkah terakhir.
Jadi, kita memiliki semua informasi yang kita butuhkan.
Kita akan menggunakan Prompt API, output terstruktur, dan sintesis ucapan untuk membuat perbandingan, lalu menampilkan informasi tersebut kepada pengguna dengan cara yang mudah dipahami.
Oke, jadi pada dasarnya, yang ingin kita ketahui adalah apakah paket ini benar-benar sama dengan informasi dari pengguna, yang merupakan pesanan sebenarnya.
Jadi, untuk melakukannya, kita juga akan membuat embedding, bukan, pada informasi dari pesan suara serta teks, mengubahnya menjadi vektor embedding yang sama seperti yang kita lakukan sebelumnya.
val callerVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(callerParcelPhotoBmp), // brown box w/ shipping label
TextPart(callerText) // "Clearance fee ¥6,800 due today."
))
)
).embedding.vector
val matchScore = cosineSimilarity(callerVector, vaultMatch.embedding)Kemudian, kita akan menggunakan matchScore. Dengan menggunakan kesamaan kosinus, kita dapat membuat perbandingan antara kecocokan yang telah kita ambil dari vault serta informasi dari penelepon tersebut.
Jadi, seperti yang Anda lihat di sini, meskipun gambarnya mungkin tidak cocok, teksnya tetap sangat membantu.
Jadi, di sinilah embedding multimodal sebenarnya cukup berharga.
Anda dapat memasukkan berbagai informasi, lalu mendapatkan makna semantik yang sama, bukan?
Jadi, embedding multimodal memungkinkan Anda memetakan semuanya ke ruang vektor yang sama.
Sekali lagi, kita akan menggunakan mode penalaran di sini untuk meningkatkan kualitas penalaran kita.
val forensicRequest = generateContentRequest(
securitySystemInstruction,
content {
image(callerParcelPhotoBmp)
image(orderEmailLensBmp)
text(forensicAuditPrompt)
}
) {
// Enable Gemini Nano 4 reasoning before it answers
enableThinking = true
}Lalu, kita juga akan menentukan class output, bukan?
@Generable(description = "Details of a visual contradiction found during an audit")
data class VisualContradiction(
@Guide(
description = "The type of contradiction",
enumValues = ["PACKAGING_MISMATCH", "STATUS_CONFLICT", "DUTY_FRAUD"],
)
val contradictionType: String,
@Guide(description = "Detailed explanation of the visual contradiction")
val explanation: String,
)Jadi, yang kita lakukan di sini adalah menyiapkan VisualContradiction dan menyiapkan beberapa enum berbeda dari berbagai jenis kemungkinan kontradiksi yang mungkin kita miliki, bukan?
Baik itu DUTY_FRAUD, PACKAGING_MISMATCH, atau STATUS_CONFLICT.
Lalu, kita akan mendapatkan penjelasan di sini.
Lalu, kita akan meneruskan semua informasi tersebut ke dalam putusan audit, bukan?
@Generable(description = "Verdict and details of a phishing audit")
data class PhishingAuditVerdict(
@Guide(
description = "The audit verdict",
enumValues = ["CONFIRMED_PHISHING_SCAM", "LEGITIMATE_HOLD"],
)
val verdict: String,
@Guide(description = "The fraud risk score from 0 to 100", minimum = 0.0, maximum = 100.0)
val fraudRiskScore: Int,
@Guide(description = "Estimated amount of money saved in USD", minimum = 0.0)
val moneySavedUsd: Int,
@Guide(description = "List of visual contradictions identified during audit", minItems = 0)
val contradictions: List<VisualContradiction>,
// @Guide(description = "Summary of the warning to be spoken to the user")
// `@Guide` annotations are optional.
val spokenWarningSummary: String,
)Jadi, Anda dapat melihat di sini bahwa, Anda tahu, ini sangat sederhana.
Kami hanya ingin tahu apakah ini scam atau bukan.
Kita mungkin memiliki fraudRiskScore tempat Anda dapat menetapkan nilai minimum dan maksimum dari 0 hingga 100.
Anda bahkan dapat memberi tahu pengguna jumlah uang yang telah Anda hemat untuk mereka jika Anda ingin terus melacaknya dari waktu ke waktu.
Kemudian, Anda juga dapat mencantumkan kontradiksi visual apa pun yang baru saja kita tentukan.
Hal lain yang perlu diperhatikan di sini adalah anotasi @Guide sebenarnya bersifat opsional.
Anda tidak perlu menetapkannya dan memberikan deskripsi semacam itu.
Alasannya adalah karena Anda mungkin tidak dapat menjelaskan apa yang terjadi hanya dengan definisi variabel.
Jadi, deskripsi tersebut dapat membantu Gemini Nano dalam menentukan fungsi spesifik yang harus dipanggil dan cara menyiapkannya agar memasukkan informasi yang tepat, di kolom yang tepat.
Jadi, setelah Anda menyiapkannya, prosesnya sangat mudah.
Anda hanya perlu menjalankan tiga baris kode di sini.
val typedRequest = generateTypedContentRequest(forensicRequest, PhishingAuditVerdict::class)
val response = generativeModel.generateContent(typedRequest)
val verdict: PhishingAuditVerdict? = response.candidates.first().responsegenerateTypedContentRequest, Anda membuat konten tersebut, lalu Anda dapat mengambil respons tersebut.
Lalu, berikut adalah contoh tampilannya, bukan?
{
"verdict": "CONFIRMED_PHISHING_SCAM",
"fraudRiskScore": 98,
"moneySavedUsd": 45,
"contradictions": [
{ "contradictionType": "DUTY_FRAUD", "explanation": "Fee demanded, but duties were prepaid." },
{ "contradictionType": "PACKAGING_MISMATCH", "explanation": "Carton has no carrier label or tracking." },
],
"spokenWarningSummary": "Scam blocked. Do not pay the 45 dollar fee. Your lens shipped duties prepaid."
}Jadi, kami mengonfirmasi bahwa ini adalah scam, kami mendapatkan skor risiko penipuan yang sangat tinggi, yang menjelaskan kontradiksi yang terlihat dalam data yang Anda ambil dibandingkan dengan informasi penelepon.
Kami juga telah menghemat sejumlah uang untuk pengguna ini, sehingga kami dapat terus melacaknya di sana.
Ya.
Jadi, hal itu merangkum semua informasi yang kita perlukan.
Lalu, langkah terakhir adalah menampilkan ini kepada pengguna dengan cara yang mudah diakses.
Jadi, jika mereka sedang dalam perjalanan, misalnya, atau mungkin mengalami gangguan penglihatan, Anda mungkin ingin membacakan informasi tersebut dengan keras, bukan meminta mereka membaca teks tersebut secara manual.
Jadi, kita akan menerapkan kemampuan text-to-speech untuk menunjukkan tampilannya.
val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
/* more code here */
}
} finally {
synthesizer.close() // safe here: the flow completes after DONE, STOP, or Error
}Jadi, sekali lagi, sangat sederhana, kita menentukan lokalitas dan bahasa, lalu meneruskan keputusan yang baru saja kita buat menggunakan @Generable dan @Guide.
Setelah itu, prosesnya akan berjalan hampir seketika.
val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
synthesizer.synthesize(ttsRequest).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate ->
/* playback finished */
if (response.status == SpeechSynthesizerResponse.Status.DONE) { }
/* response.errorCode is a TextToSpeech.ERROR_* code */
is SpeechSynthesizerResponse.Error -> { }
else -> {}
}
}
}
} finally {
synthesizer.close() // safe here: the flow completes after DONE, STOP, or Error
}Diperlukan waktu kurang dari 120 milidetik di sebagian besar perangkat untuk menghasilkan respons tersebut.
Kami akan menghapus memori yang diperlukan.
Lalu, jika perlu, kita juga dapat menetapkan penggantian, bukan, menggunakan API platform.
val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
synthesizer.synthesize(ttsRequest).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate ->
/* playback finished */
if (response.status == SpeechSynthesizerResponse.Status.DONE) { }
/* response.errorCode is a TextToSpeech.ERROR_* code */
is SpeechSynthesizerResponse.Error -> {
/* handle fallback to platform API */
}
else -> {}
}
}
}
// more code here ...Meskipun masih menggunakan suara robot, setidaknya pengguna memiliki sesuatu untuk berinteraksi jika mereka tidak dapat membaca.
David Chou
Oke.
Jadi, itu tadi banyak sekali, tetapi untuk merangkum apa yang telah kita lakukan, kita memanfaatkan semua API yang berbeda ini di seluruh proses penerimaan, triase, pengambilan, dan audit.
Anda tahu, menggunakan API pengenalan dan terjemahan ucapan, prompt API dan pemanggilan fungsi, embedding, dan AppSearch, serta output terstruktur dan sintesis ucapan.
Jadi, ini hanyalah contoh cara Anda dapat memanfaatkan semua kemampuan ini secara koheren dan menjalankannya sepenuhnya di perangkat.
Lalu kembali ke apa yang kita bicarakan di awal, ada manfaat besar dari menjalankan ini, bukan?
Jika Anda benar-benar menjalankannya di perangkat Anda saat ini, Anda akan melihat bahwa proses ini jauh lebih cepat di perangkat pada sebagian besar hardware.
Anda tidak perlu membayar apa pun, dan Anda berpotensi menghemat ratusan ribu dolar jika menjalankan ini sepanjang waktu.
Lalu, fitur ini tersedia, bukan, terlepas dari apakah pengguna berada di pegunungan atau di ruang bawah tanah dan tidak memiliki akses ke internet.
Dan sepenuhnya bersifat pribadi karena Anda tidak perlu mengirim informasi apa pun ke cloud.
Dengan semua kemampuan ini, silakan lihat dokumentasi, lihat kode QR, atau buka link.
Semua ini adalah kemampuan yang telah kami luncurkan selama sekitar enam bulan terakhir.
Terima kasih banyak telah meluangkan waktu untuk menghadiri diskusi ini.
Terima kasih telah meluangkan waktu bersama kami.
Jadi, terima kasih banyak.
val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)
val audioFlow = flow {
emit(TranslationAudio(Locale.JAPANESE, courierVoicemailBytes))
}
val request = SpeechTranslatorRequest.Builder(audioFlow).build()
translator.startTranslation(request).collect { response ->
if (response is SpeechTranslatorResponse.Text) {
translatedCourierClaim = response.translatedText
}
}val recognizer = SpeechRecognition.getClient(
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
)val recognizer = SpeechRecognition.getClient(
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
)
val request = speechRecognizerRequest {
audioSource = AudioSource.fromMic()
recognitionOptions { enableWordTiming = true } // optional
}
recognizer.startRecognition(request).collect { response ->
when (response) {
is SpeechRecognizerResponse.PartialTextResponse ->
liveCaption = response.text
is SpeechRecognizerResponse.FinalTextResponse ->
planAndCallVaultTool(translatedCourierClaim, response.text)
}
}retrieveOrderAndDeliveryPhotos(
searchQuery = "camera lens customs",
claimedFeeUsd = 45,
comparePackagePhotos = true
)// Keep SystemInstruction static across Turn 1 & Turn 2:
val securitySystemInstruction = SystemInstruction(
"""
You are an on-device parcel & phishing security auditor.
1. Cross-check packaging, carrier labels and tracking across all images.
2. Verify customs fee claims against local DDP (Delivered Duty Paid) order records.
3. Flag any payment demand when the order record shows duties prepaid.
""".trimIndent()
)// Pull in the Speech Translation (AST) & Speech Recognition (ASR) outputs:
val turn1Prompt = """
Translated voicemail: $translatedCourierClaim
Incoming text message: $callerText
User's spoken question: $userQuestion
""".trimIndent()
val turn1Request = generateContentRequest(
securitySystemInstruction,
TextPart(turn1Prompt)
) { /* enableThinking & setTools */ }// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {
}// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {
/**
* Searches local orders and order images in AppSearch.
* @param searchQuery Semantic query to match the order
* @param claimedFeeUsd Customs fee claimed in USD
* @param comparePackagePhotos Whether to fetch order images to compare
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun retrieveOrderAndDeliveryPhotos(
searchQuery: String,
claimedFeeUsd: Int,
comparePackagePhotos: Boolean
): VaultMatch = withContext(Dispatchers.IO) { executeVaultSearch(searchQuery) }
}val vaultTools = AppFunctionTools.getClient().getAppFunctions()
val turn1Request = generateContentRequest(securitySystemInstruction, TextPart(turn1Prompt)) {
enableThinking = true
setTools(vaultTools)
}
val call = generativeModel.generateContent(turn1Request)
.candidates.first().functionCalls.first()val embedModel = EmbeddingGeneration.getEmbeddingModels().first()
val docVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(orderEmailLensBmp), // the lens photo in the email
TextPart(orderEmailBody) // the order details
))
)
).embedding.vectorval embedModel = EmbeddingGeneration.getEmbeddingModels().first()
val docVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(orderEmailLensBmp), // the lens photo in the email
TextPart(orderEmailBody) // the order details
))
)
).embedding.vector
val doc = GenericDocument.Builder<GenericDocument.Builder<*>>(
"orders", "order_jp_904", "OrderRecord"
)
.setPropertyString("summary", "Shipped DDP, duties prepaid ($0 owed). In transit.")
.setPropertyString("photoUris", "order_email_lens.png")
.setPropertyEmbedding(
"embedding",
EmbeddingVector(docVector, embedModel.getModelSignature())
).build()suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
}suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
val spec = SearchSpec.Builder()
.addEmbeddingParameters(EmbeddingVector(qVec, embedModel.getModelSignature()))
.setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true).build()
}suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
val spec = SearchSpec.Builder()
.addEmbeddingParameters(EmbeddingVector(qVec, embedModel.getModelSignature()))
.setDefaultEmbeddingSearchMetricType(SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_COSINE)
.setRankingStrategy("sum(this.matchedSemanticScores(getEmbeddingParameter(0)))")
.setListFilterQueryLanguageEnabled(true).build()
val results = appSearchSession.search(
"semanticSearch(getEmbeddingParameter(0), 0.68)", spec
)
// Cleanly close the cursor.
return results.use { it.nextPageAsync.await().first().genericDocument.toVaultMatch() }
}val callerVector = embedModel.generateEmbedding(
EmbeddingRequest(
Content(listOf(
ImagePart(callerParcelPhotoBmp), // brown box w/ shipping label
TextPart(callerText) // "Clearance fee ¥6,800 due today."
))
)
).embedding.vector
val matchScore = cosineSimilarity(callerVector, vaultMatch.embedding)val forensicRequest = generateContentRequest(
securitySystemInstruction,
content {
image(callerParcelPhotoBmp)
image(orderEmailLensBmp)
text(forensicAuditPrompt)
}
) {
// Enable Gemini Nano 4 reasoning before it answers
enableThinking = true
}@Generable(description = "Details of a visual contradiction found during an audit")
data class VisualContradiction(
@Guide(
description = "The type of contradiction",
enumValues = ["PACKAGING_MISMATCH", "STATUS_CONFLICT", "DUTY_FRAUD"],
)
val contradictionType: String,
@Guide(description = "Detailed explanation of the visual contradiction")
val explanation: String,
)@Generable(description = "Verdict and details of a phishing audit")
data class PhishingAuditVerdict(
@Guide(
description = "The audit verdict",
enumValues = ["CONFIRMED_PHISHING_SCAM", "LEGITIMATE_HOLD"],
)
val verdict: String,
@Guide(description = "The fraud risk score from 0 to 100", minimum = 0.0, maximum = 100.0)
val fraudRiskScore: Int,
@Guide(description = "Estimated amount of money saved in USD", minimum = 0.0)
val moneySavedUsd: Int,
@Guide(description = "List of visual contradictions identified during audit", minItems = 0)
val contradictions: List<VisualContradiction>,
// @Guide(description = "Summary of the warning to be spoken to the user")
// `@Guide` annotations are optional.
val spokenWarningSummary: String,
)val typedRequest = generateTypedContentRequest(forensicRequest, PhishingAuditVerdict::class)
val response = generativeModel.generateContent(typedRequest)
val verdict: PhishingAuditVerdict? = response.candidates.first().response{
"verdict": "CONFIRMED_PHISHING_SCAM",
"fraudRiskScore": 98,
"moneySavedUsd": 45,
"contradictions": [
{ "contradictionType": "DUTY_FRAUD", "explanation": "Fee demanded, but duties were prepaid." },
{ "contradictionType": "PACKAGING_MISMATCH", "explanation": "Carton has no carrier label or tracking." },
],
"spokenWarningSummary": "Scam blocked. Do not pay the 45 dollar fee. Your lens shipped duties prepaid."
}val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
/* more code here */
}
} finally {
synthesizer.close() // safe here: the flow completes after DONE, STOP, or Error
}val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
synthesizer.synthesize(ttsRequest).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate ->
/* playback finished */
if (response.status == SpeechSynthesizerResponse.Status.DONE) { }
/* response.errorCode is a TextToSpeech.ERROR_* code */
is SpeechSynthesizerResponse.Error -> { }
else -> {}
}
}
}
} finally {
synthesizer.close() // safe here: the flow completes after DONE, STOP, or Error
}val synthesizer = SpeechSynthesis.getClient(SpeechSynthesizerOptions.builder(Locale.US).build())
try {
val voice = synthesizer.getSupportedVoices(Locale.US).firstOrNull()
if (voice != null) {
val ttsRequest = SpeechSynthesizerRequest(
input = SpeechSynthesizerRequest.Input.Text(verdict.spokenWarningSummary, voice),
audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
synthesizer.synthesize(ttsRequest).collect { response ->
when (response) {
is SpeechSynthesizerResponse.StatusUpdate ->
/* playback finished */
if (response.status == SpeechSynthesizerResponse.Status.DONE) { }
/* response.errorCode is a TextToSpeech.ERROR_* code */
is SpeechSynthesizerResponse.Error -> {
/* handle fallback to platform API */
}
else -> {}
}
}
}
// more code here ...