Akıllı Uygulamalar

Cihaz üzerinde akıllı veri işleme: Android ML Kiti API'leri kullanılarak optimize edilmiş performans ve kalite

Hoparlörler

Hakkında

Konuşma hakkında

Bu oturumda, cihaz üzerinde üretken yapay zekanın bir uygulama için ne zaman ideal olduğunu anlamak amacıyla ML Kiti API'lerini kullanan farklı üretken yapay zeka kullanım alanlarını inceleyeceğiz. Ayrıca, mevcut üretken yapay zeka kullanım alanlarını değerlendirerek cihaz üzerinde modellerin kalite eşiğini karşılayıp karşılamadığını nasıl anlayacağınızı öğreneceğiz.

Özet

  • Cihaz üzerinde çalışan yapay zeka, bulut tabanlı çıkarıma kıyasla hız ve gizlilik açısından önemli avantajlar sunar. Dahili testler, modelleri bulutta çalıştırmaya kıyasla 2 kat daha hızlı olduğunu gösteriyor.
  • Gemini Nano'nun bellek kullanımı 3,6 GB'tan 1,1 GB'a düştü. Temel optimizasyonlar arasında 2 bitlik niceleme ve paylaşılan KV önbelleği yer alır.
  • Önek önbelleğe alma ve tahmine dayalı kod çözme, çıkarım sürecini önemli ölçüde hızlandırır. Sistem istemlerinin yeniden kullanılması, işlenen jeton sayısını %60 azaltırken daha küçük bir taslak model, ortalama 2 kat hız artışı sağlar.
  • Cihaz üzerindeki yeni özellikler, konuşma çevirisi ve işlev çağrısı aracılığıyla uygulama etkileşimlerini genişletir. Düşünme modu eklendiğinde Gemini Nano 4'te araç çağırma doğruluğu 100 üzerinden 13 puan artar.
  • Çok formatlı yerleştirmeler, AppSearch ile birleştirilerek verileri kosinüs benzerliği aracılığıyla depolar ve alır. Uygulamanız, arama mantığını yerel olarak yürüterek kullanıcı gizliliğini ve veri güvenliğini korur.

Kaynaklar

Gemini Notebook

Konular

  • on-device-ai
  • ml-kit
  • gemini-nano
  • aicore
  • işlev çağrısı
  • yerleştirmeler
  • konuşma çevirisi

Transkript

Cihaz üzerinde yapay zekaya giriş

  • David Chou

    YouTube'u

  • Herkese merhaba.

  • Adım David Chou.

  • Android AICore'da ürün yöneticisiyim.

  • Cihaz üzerinde yapay zeka özelliklerimiz hakkında konuşmak için sabırsızlanıyorum.

  • Bugün birkaç temel konuyu ele alacağız.

  • İlk olarak, özellikle buluttaki yapay zeka gelişmelerinin hızlanması göz önüne alındığında, neden cihaz üzerinde yapay zeka kullanmanız gerektiğini düşünelim.

  • İkincisi, cihaz üzerinde çalışan bu yapay zeka modellerini son derece güçlü hale getirmek için geçtiğimiz yıl yaptığımız bazı iyileştirmeleri düşünmek.

  • Son olarak, cihaz üzerinde yapay zekayı kullanarak tüm bu avantajlardan nasıl yararlanabileceğinizi gösteren uçtan uca bir örnekten bahsedeceğiz.

  • Öncelikle, neden cihaz üzerinde yapay zeka kullanmalısınız?

  • Bunun 4 temel nedeni var.

  • Birincisi hız, ikincisi maliyet, üçüncüsü kullanılabilirlik ve dördüncüsü gizliliktir.

  • Hız açısından bakıldığında, cihaz üzerinde yapay zeka çıkarımının artık bulutta çalıştırmaktan daha hızlı olabileceği bir noktaya geldiğini görebilirsiniz. Bunun nedeni, kullanıcının cihazı ile bulut arasında bilgi alışverişi yapmanız gerekmemesidir.

  • Test ettiğimiz kullanım alanlarının çoğunda, cihaz üzerinde yapay zekanın buluta kıyasla 2 kat daha hızlı olduğunu görüyoruz.

  • İkincisi, maliyeti düşünmektir.

  • Günde bir milyon çıkarım yaptığınızı düşünelim.

  • Cihaz üzerinde yapay zekayı bulutta çalıştırmak yerine kullanıcının cihazında çalıştırırsanız yüz binlerce dolar tasarruf edebilirsiniz.

  • Üçüncüsü ise çevrimdışı kullanılabilirliktir.

  • Bu yılın başlarında bir anket gönderdik ve görüştüğümüz geliştiricilerin %62'si, cihaz üzerinde yapay zekayı kullanmanın en önemli üç nedeninden birinin çevrimdışı kullanılabilirlik olduğunu söyledi.

  • Böylece kullanıcılarınız ister 10.000 metre yükseklikte ister Tokyo istasyonunun bodrum katında olsun, internet erişimi olmasa bile tüm bu özelliklerden yararlanabilir.

  • Son olarak, geliştiricilerle yaptığımız ankette gizliliğin en önemli nedenlerden biri olduğunu gördük.

  • Yani %72'si bunu ilk üç nedenden biri olarak sıraladı.

  • %40'ı birinci sıraya koydu.

  • Kullanıcılar mesajlarını, sesli notlarını, fotoğraflarını, hatta finansal veya tıbbi bilgilerini gizli tutmak ister. Bu nedenle, çıkarım işlemini cihaz üzerinde çalıştırmak onlar için çok değerli olacaktır.

  • Tüm bu avantajlardan yararlanabilmeniz için ML Kit'i geliştirdik.

  • Bu süreci olabildiğince basitleştirmeye çalışıyoruz.

  • Bu nedenle, herhangi bir şikayetiniz olursa lütfen bana ulaşın.

  • Konuşmanın sonunda iletişim bilgilerimi paylaşacağım.

  • Kullanım alanlarınızı nasıl destekleyebileceğimizi öğrenmek isteriz.

  • Bu nedenle, başlangıç olarak konuşma ve yaratıcı kullanım alanları gibi bir dizi özelliği etkinleştirdik.

  • Örneğin, gözden geçirme, yeniden yazma ve esnek çok formatlı istem yazma gibi.

  • Bu nedenle, bunu mümkün olduğunca kullanılabilir hale getirmeye çalışıyoruz.

  • Bu nedenle, birçok kullanıcımız ve geliştiricimiz bu özelliği kullanmaya başladı.

  • Şu anda, üretimde bu üretken yapay zeka özelliklerini kullanan ve 10 milyon kez indirilen 9 üçüncü taraf uygulamamız var.

  • Hem birinci taraf hem de üçüncü taraf uygulamalarımızda çok daha fazla ölçeklendirme görüyoruz. Yapay zeka üretim özellikleri için uygulama lansmanlarında çeyrekten çeyreğe %110 büyüme elde ettik.

  • Bu nedenle, mesajlaşma, reklam öğesi ve finans alanlarında mevcut API'lerimizi denemenizi öneririz.

  • Olasılıkların sonsuz olduğunu düşünüyoruz. Bu kullanım alanlarının çoğunda muhtemelen bizden daha iyi bir hayal gücüne sahipsiniz. Bu nedenle, sizinle iş ortaklığı yaparak birlikte nasıl çalışabileceğimizi öğrenmek isteriz.

  • Artık genel avantajları anladınız.

  • Erişimin gerçekte nasıl olduğuyla ilgili sorularınız olabilir.

  • Cihazdaki performans nasıl?

  • Hızdan bahsettiğimizde, aslında ne kadar iyi?

  • Modelin kalitesi de önemli, değil mi?

  • Bunları tek tek inceleyeceğiz.

  • Erişim açısından, şu anda 175 milyon AICore cihazda kullanılabilir durumdayız.

  • Bu sayının önümüzdeki yıl önemli ölçüde artarak yüz milyonlarca başka cihazı da kapsamasını bekliyoruz.

  • Bunu mümkün kılan şeylerden biri, çok basit bir donanım oluşturmuş olmamız.

  • Bu nedenle, yalnızca ML Kit API'yi entegre etmeniz yeterlidir.

  • AICore'da giriş ve çıkış güvenlik sinyallerini yönetmek, modeli ağırlıklarla birlikte indirmeyi yönetmek ve çalışma zamanında nasıl kurulacağını düşünmek için çok fazla karmaşıklık vardır.

  • Bu nedenle, ML Kit'i kullanımı kolay hale getirmeyi hedefliyoruz. Böylece siz yalnızca harika bir deneyim sunmaya odaklanabilirsiniz, gerisini biz hallederiz.

Gemini Nano performansını ve karşılaştırmalarını optimize etme

  • David Chou

    Ayrıca, bu üretken yapay zeka API'lerinin çoğunu destekleyen ana model olan Gemini Nano'yu çalıştırmak için gereken RAM miktarını önemli ölçüde azalttığımızı duyurmaktan mutluluk duyuyoruz.

  • Bu nedenle, 3,6 GB RAM kullanımını 1,1'e düşürdük.

  • Bunu mümkün kılmak için 4 temel iyileştirme yaptık.

  • İlk olarak, 4 bit yerine 2 bitlik niceleme kullanılıyor.

  • Yani bit sayısının yarıya inmesi, modelden geçirmeniz gereken bayt sayısının da yarıya inmesi anlamına gelir.

  • İkincisi, paylaşılan bir KV önbelleği kullanmaktır.

  • Bu nedenle, modelin ilk 20 katmanının bunu 35 MB'lık pakette paylaşmasına izin vererek bu boyutu önemli ölçüde azalttık. Yani bu çok küçük paket boyutunda 16.000 jetonluk bağlam boyutuna erişmeye devam edebilirsiniz.

  • Üçüncüsü ise geç yüklenen görüntü ve ses kodlayıcıları etkinleştirmektir.

  • Bu nedenle, gördüğümüz metin kullanım alanlarının çoğu için başlangıçta bunları yüklemeniz gerekmez.

  • Bu da yarım gigabayt RAM tasarrufu sağlar.

  • Son olarak, pil ömrünü uzatmak için NPU saat ölçeklendirmesini de etkinleştirdik.

  • Bu, NPU'nun güç tasarrufu yapmak için gerektiği kadar hızlı çalışabileceği ve kullanım alanlarınızda yüksek kalite sunmaya devam edeceği anlamına gelir.

  • Hızlandırma konusunda yaptığımız birkaç iyileştirme daha var.

  • İlk olarak, önek önbelleğe almayı etkinleştirebilirsiniz.

  • Bu örnekte sistem isteminin birden fazla sıra için yeniden kullanıldığını görebilirsiniz.

  • Bu nedenle, bu verileri işlemek çok maliyetli olur.

  • Ancak önek önbelleğe alma sayesinde, sistem istemini tekrar tekrar işlemeniz gerekmiyor, değil mi?

  • Bunu önek önbelleğine kaydedebilirsiniz. Bu örnekte, iletmeniz gereken jetonların %60'ını kaydetmiş olursunuz. Bu da kullandığınız cihazda %60 hız artışı anlamına gelir.

  • Yaptığımız ikinci iyileştirme, spekülatif kod çözme ile ilgilidir.

  • Bu örnekte, Gemini Nano'nun her geçişte bir jeton ürettiğini görebilirsiniz.

  • Bunun nedeni, LLM'lerin otoregresif olmasıdır. Yani bir sonraki jetonları bulmak için daha önce oluşturulan jetonlara bakmaya devam etmeleri gerekir.

  • Bu sorunu çözmek için çok daha hızlı bir şekilde bir dizi jeton oluşturan bir taslak oluşturucu model geliştirdik.

  • Bu taslak oluşturucu model, Gemini Nano'dan çok daha küçüktür. Bu nedenle, tek seferde bir grup jeton oluşturabilir ve bunları Gemini'a ileterek hangilerinin Gemini Nano'nun tahminleriyle uyumlu olduğunu görebilir.

  • Bu örnekte, ilk geçişte altı jeton, bir sonrakinde beş jeton oluşturduğunu ve üçüncü turda işlemin tamamlandığını görebilirsiniz.

  • İncelediğimiz birçok dahili kullanım alanında, bu durumun ortalama 2 kat hızlanmaya yol açtığını görüyoruz.

  • Donanımınıza bağlı olarak daha hızlı iyileştirmeler alabilirsiniz.

  • Anladım.

  • Hız konusuna dönecek olursak, 5G ağında bile cihaz üzerinde ve bulutta bu önemli hız artışını görebilirsiniz.

  • Bu verileri buluta yüklemenin 1.000 ila 1.300 giriş jetonu için yalnızca bir saniye sürdüğünü görebilirsiniz. Dolayısıyla, bulut yolculuğunda önceden doldurma ve kod çözme daha hızlı olsa da uçtan uca yolculuk, cihaz üzerinde çalışırken hâlâ önemli ölçüde daha hızlıdır.

  • Kaliteye gelince, bu 2B modelin aslında geçen yılın 27B modellerine karşı performans gösterdiğini duyurmaktan mutluluk duyuyoruz.

  • Günümüzdeki öncü modeller kadar performanslı olmasa da, cihaz üzerinde çalışan bu yapay zeka modellerinde önemli bir ilerleme kaydedildiğini görüyoruz.

  • Standart bir bilgi karşılaştırması olan MMLU-Pro'da 2B modelinin, geçen yılki 27B parametre modeli kadar iyi olduğunu görebilirsiniz.

  • Ayrıca, lisansüstü fen bilimleri, akıl yürütme ve temsilci araç kullanımı karşılaştırmalarında bu 2B model, geçen yılki modelin onda birinden daha küçük olmasına rağmen daha iyi performans gösteriyor.

  • Bu trendlerin devam etmesini bekliyoruz.

  • Bu iyileştirmeleri modele uygulamaya devam edebilmek ve bu sayede en iyi değeri elde etmenizi sağlamak için GDM ekibiyle yakın bir şekilde çalışmaya devam ediyoruz.

Yeni Gemini API özelliklerine genel bakış

  • David Chou

    Anladım.

  • Birçok avantajdan ve yeni iyileştirmeden bahsettim. "Bu modellerle neler yapabiliriz?" diye soruyor olabilirsiniz.

  • API'de de bu tür özellik iyileştirmeleri yapıyoruz.

  • Sistem talimatlarıyla bir karakter belirleyebilirsiniz.

  • Çoklu resim girişi sayesinde aynı anda birden fazla resim yükleyebilirsiniz.

  • Bu yılın başlarında, akıl yürütme problemlerine ve araç çağırmaya daha fazla zaman ayırabilmeniz için düşünme modunu da etkinleştirdik.

  • Son olarak, yapılandırılmış çıkış sayesinde bir Kotlin sınıfına açıklama ekleyip özel şemanızla eşleşen bir nesne alabilirsiniz.

  • Bunun dışında, geliştiricilere yapmak istedikleri yeni iyileştirmeleri de sormaya çalışıyoruz.

  • Ankete katılan geliştiricilerin %91'i, bu özelliklerden biri mevcut olsaydı bir deneyim başlatacaklarını söyledi.

  • Bu haftanın başlarında konuşma çevirisi, yerleştirmeler, metin okuma ve işlev çağrısı olmak üzere dört özelliği kullanıma sunduk.

  • Konuşma çevirisiyle 20'den fazla dildeki sesleri metne dönüştürebilirsiniz.

  • Yerleştirmeler sayesinde, uygulamanızın içeriğinde yalnızca anahtar kelime araması yapmak yerine semantik anlama göre arama yapabilirsiniz.

  • Metin okuma özelliği sayesinde artık deneyimlerinizde insan sesine benzer sesler kullanabilirsiniz. Böylece robotik sesler kullanmak zorunda kalmazsınız.

  • Son olarak, işlev çağrısı sayesinde uygulamanızın kullanıcılarınız adına işlem yapmasına izin verebilirsiniz.

  • Bu nedenle, deneyimlerinde uygulama içi temsilcileri nasıl oluşturabileceklerini görmek için birkaç geliştiriciyle iş ortaklığı yapmaya başladık ve önümüzdeki yılın sonlarında kullanıma sunmayı umuyoruz.

Uçtan uca dolandırıcılık tespiti açıklamalı kılavuzu

  • David Chou

    Tüm bu özellikler göz önünde bulundurulduğunda, bu özelliklerin tamamını kullanarak oluşturduğumuz uçtan uca bir örneği inceleyelim.

  • Örneğin, Japonya'da seyahat eden Amerikalı bir fotoğrafçı olduğunu düşünelim.

  • Fotoğraftan da görebileceğiniz gibi, yeterli sayıda kamera veya lensleri yok.

  • Bu nedenle yeni bir lens sipariş edip kaldığı otele gönderiyor.

  • Ancak bir sorun var: Gün içinde bir sesli mesaj alıyor ve paketini gün sonuna kadar teslim alabilmesi için 6.800 yen gümrük ücreti ödemesi gerektiği söyleniyor.

  • Ayrıca, bekletilen paketin fotoğrafını içeren bir kısa mesaj da almışlar.

  • Bu durumda kullanıcı, dolandırıcılık tespiti koruması istiyor, değil mi?

  • Sesli mesajların meşru mu yoksa sahte mi olduğunu nasıl anlayabileceklerini öğrenmek istiyorlar.

  • Bu nedenle, tüm bilgileri gizli tutmak, bilgilerin temellendirilmiş ve açıklanabilir olduğundan emin olmak istiyoruz.

  • Bu nedenle, Japonca metni anlayabildiklerinden ve erişilebilirlik ihtiyaçları varsa mevcut dil özelliklerini kullanabildiklerinden emin olacağız.

  • Burada dört adım olacak, değil mi?

  • İlk adım, sesli mesajı metne dönüştürmektir.

  • Bunun için Speech Translation API'yi kullanacağız.

  • Ardından, isterlerse soru sorabilecekler. Bu nedenle Speech Recognition API'yi kullanacağız.

  • Ardından, kullanıcının ihtiyaç duyduğu bilgileri alabilmesi için gerekli olan işlevleri tanımlamak istiyoruz.

  • Bu nedenle Prompt API ve işlev çağrısını kullanacağız.

  • Bu işlem, kullanıcıya herhangi bir şey gösterilmeden arka planda çalışır.

  • Üçüncü olarak, yerleştirme API'sini ve AppSearch'ü kullanarak bu bilgileri nasıl depolayacağımızı ve alacağımızı öğreneceğiz.

  • Bu işlem arka planda da çalışır.

  • Son olarak, Prompt API, yapılandırılmış çıkış ve konuşma sentezi kullanarak topladığımız bilgilere göre bir karar vereceğiz.

  • Anladım.

  • Hadi, başlayalım o zaman.

  • Konuşma çevirisi ve konuşma tanıma özelliklerini kullanacağız.

  • İlk adım, sesli mesajı ve metni alıp faydalı bilgilere dönüştürmek, değil mi?

  • Kullanıcı, "6.800 yenlik bu ücreti ödemeniz gerekiyor.

  • Gümrük vergileri önceden ödenmemiş olabilir. Bu durumda ödemeyi sizin yapmanız gerekir."

  • Bu nedenle, bu çeviriyi yapabilmek istiyorlar, değil mi?

  • Burada belirtmek istediğimiz noktalardan biri, hem çevirinin hem de kullanıcının bilgiyle ilgili sorusunun aynı ses kodlayıcıyı kullanarak Gemini Nano 4'e iletilmesidir. Çünkü ses kodlayıcı 100'den fazla dilde eğitilmiştir.

  • Böylece hem bu çeviriyi hem de kullanıcının sorusunun metni için ASR'yi alabilirsiniz.

  • Çeviri için tek yapmamız gereken bir kaynak ve hedef dil belirlemek.

    val translator = SpeechTranslation.getClient(
        SpeechTranslatorOptions.Builder(
            Locale.JAPANESE, Locale.US
        ).build()
    )
  • Bu örnekte, Amerikan İngilizcesinin yanı sıra Japonca kullanacağız.

  • Baytları doğrudan konuşma çevirmenine ileten bir Kotlin akışı oluşturacağız.

    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
        }
    }
  • Ardından, çevrilen metni kullanarak yanıtı toplayabiliriz.

  • Anladım.

  • Harika.

  • Sesli mesajda ne dendiğini artık anlıyoruz, değil mi?

  • Kamera lensi paketi var.

  • Gümrükte tutuluyor.

  • 6.800 yen tutarındaki ücreti ödemeleri gerekir.

  • Kullanıcı, bu konuyla ilgili soru sormak istiyor.

  • Anladım.

  • Peki bu gümrükte bekletme durumu gerçekten doğru mu?

  • Bu örnekte, ASR API'yi kullanacağız.

    val recognizer = SpeechRecognition.getClient(
        speechRecognizerOptions {
            locale = Locale.US
            preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
        }
    )
  • Biz yine de yerel ayar olarak Amerikan İngilizcesi'ni ayarlıyoruz.

  • Ardından, ses kodlayıcıdan yararlanan gelişmiş moda ayarlayabiliriz.

  • Ardından bu bilgileri de iletebilir, nihai yanıtı toplayabilir ve bunu Gemini Nano'ya ileteceğimiz bilgilere dönüştürebiliriz.

    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)
        }
    }
  • Anladım.

  • Böylece ilk kabul aşamasını tamamlamış olduk.

  • Gördüğünüz gibi, çok fazla kod satırı gerekmedi.

Prompt API ve işlev çağrısını kullanarak girişleri önceliklendirme

  • David Chou

    İkinci bölüm ise triyajdır. Bu bölümde, ne tür bilgilere ihtiyaç duyulduğunu belirlemek için Prompt API'den ve işlev çağrısından nasıl yararlanılacağı düşünülür.

  • Anladım.

  • Bunun mimarisini oluşturmak için önce bazı sistem talimatları ayarlayacağız.

  • Ardından sesli mesaj, metin ve fotoğrafın yanı sıra kullanıcının sorusunu da Gemini Nano'ya giriş olarak ileteceğiz.

  • Ardından, ihtiyacımız olan bilgileri alan işlevleri tanımlayacağız.

    retrieveOrderAndDeliveryPhotos(
      searchQuery = "camera lens customs",
      claimedFeeUsd = 45,
      comparePackagePhotos = true
    )
  • Başlamak için sistem talimatlarını ayarlayacağız.

    // 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()
    )
  • Burada, güvenlik denetçisi oluşturmanın çok basit bir örneğini görebilirsiniz. Bu örnekte, ambalaj ve etiketleri çapraz kontrol edeceğimiz, gümrük ücretlerine bakacağımız ve gerekli ödeme taleplerini işaretleyeceğimiz belirtiliyor.

  • İstem optimizasyonunun, çok daha iyi kalite elde etmenize yardımcı olacağını görebilirsiniz.

  • Sistem talimat grubu hazır olduğunda, önceki adımda elde ettiğimiz tüm içeriği (sesli mesajın ve metnin çevirisi ile kullanıcının sorusu) bu TextPart alanına aktarabilir ve sistem talimatlarıyla birlikte bu bilgileri alabiliriz.

    // 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 */ }
  • Ardından kullanabileceğimiz bir işlev tanımlamak istiyoruz.

  • Bu örnekte, uygulama işleviyle entegrasyon yaptığımızı duyurmaktan heyecan duyuyoruz.

  • Bu konuda bilginiz varsa muhtemelen bunun nasıl çalıştığını kabaca biliyorsunuzdur.

  • Burada bir AppFunctionService giriş noktası oluşturup uygulama işlevi tanımlıyoruz.

    // Registered in AndroidManifest.xml
    @AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
    abstract class ScamInvestigationTools : AppFunctionService() {
    
    }
  • Bu işlemin, bir sistem temsilcisi için uygulama işlevleri ayarlarken yaptığınız işlemin aynısı olduğunu görebilirsiniz.

  • searchQuery parametrelerini, talep ücretini ve paket fotoğraflarını karşılaştırıp karşılaştırmayacağımızı ayarladınız. Bunlar örnek parametrelerden bazıları, değil mi?

    // 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) }
    }
  • Farklı olanları tanımlayabilirsiniz.

  • Son olarak, bu işlev için daha sonra tanımlayacağımız bir executeVaultSearch işlevi var.

  • Burada değinmek istediğim noktalardan biri, düşünme modunun kullanılabilir olması.

    val vaultTools = AppFunctionTools.getClient().getAppFunctions()
    val turn1Request = generateContentRequest(securitySystemInstruction, TextPart(turn1Prompt)) {
    
        enableThinking = true
        setTools(vaultTools)
    
    }
    val call = generativeModel.generateContent(turn1Request)
        .candidates.first().functionCalls.first()
  • Araç çağrısı yaparken bu parametreyi kullanmanızı önemle tavsiye ederiz.

  • Bunun nedeni, Gemini Nano 4'te araç çağrısı çalıştırıldığında doğrulukta 100 üzerinden 13 puanlık bir iyileşme olduğunu gösteren bir dizi test yapmış olmamızdır.

  • Hatta bazı durumlarda bu oran çok daha yüksek olabilir. Bu nedenle, bu yöntemle çok daha iyi sonuçlar elde edebilirsiniz.

  • İşlev çağrısı hakkında daha fazla bilgi edinmek isterseniz meslektaşlarım Jolanda ve Victoria'nın daha sonraki oturumuna katılabilirsiniz.

  • Bu, uygulamanızdaki ajan tabanlı yeteneklerle ilgilidir.

  • Victoria da kalabalığın arasında bir yerde oturuyor olabilir. Ona da merhaba diyebilirsiniz.

Yerleştirmeler ve AppSearch ile sipariş verilerini alma

  • David Chou

    Tamam, güzel.

  • İlk iki adımı tamamladık.

  • Şimdi, bilgi almanın nasıl çalıştığından bahsedelim.

  • Bu nedenle, yerleştirmeleri ve AppSearch'ü kullanacağız.

  • Kullanıcı yolculuğunda birkaç adım geriye gidip lens siparişi verilen noktaya bakarsak bu bilgileri dizine ekleyerek hem kullanıcının hem de geliştiricinin erişebilmesini sağlamak isteriz.

  • Bu nedenle, burada sipariş paketi numarasıyla ilgili ayrıntılı bilgiler yer alır.

  • Takip numarası da var.

  • Gönderildiği yerle ilgili bazı ayrıntılar içerir.

  • Burada, gümrük vergilerinin önceden ödendiği belirtiliyor.

  • Bu nedenle, bu bilgileri bizim için önemli olabilecek diğer bilgilerle birlikte dizine eklemek için çok formatlı bir yerleştirme oluşturacağız.

  • Şimdi nasıl göründüğüne bakalım.

    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
  • Sipariş e-postasını alırız.

  • Yine çok basit bir şekilde, yalnızca bir resim ve bir metin iletiyoruz ancak burada dizine eklemek istediğiniz daha fazla içerik olabilir.

  • Ardından yerleştirme vektörünü oluşturacağız.

  • Bunu yaptıktan sonra AppSearch'ü kullanarak bir doküman oluşturacağız ve ardından bu dokümanı oluşturmak için gerekli olduğunu düşündüğümüz özellikleri ayarlayacağız.

    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()
  • Burada dikkat edilmesi gereken bir nokta var. Çok formatlı yerleştirmeler kullanmak istememizin nedeni, ortak kelimeler olmasa bile resimler ve metinler benzer içeriklerle ilgili olduğu için anlamsal benzerliği yakalayabilmemizdir.

  • Bu nedenle, ortak kelimesi olmayan öğeleri bulmak için kosinüs benzerliğini kullanabiliriz.

  • Daha önce bahsettiğimiz executeVaultSearch işlevi bu.

    suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
        val qVec = embedModel.generateEmbedding(
            EmbeddingRequest(
                content = Content(listOf(TextPart(searchQuery)))
            )
        ).embedding.vector
    
    }
  • Tanımladığımız uygulama işlevi tarafından çağrılan şey budur.

  • Bu örnekte, Gemini Nano tarafından oluşturulan arama sorgusunu iletiyoruz.

  • Ardından, kosinüs benzerliği sıralama stratejisini kullanacağız.

    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()
    
    }
  • this.matchedSemanticScores ise bu bilgileri edinmemize yardımcı olur.

  • Son olarak, bilgi getirmek için minimum kosinüs benzerliğinin ne olduğunu belirlemek üzere 0, 68 eşiğini belirteceğiz.

    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() }
    }
  • Bu nedenle, kendi kullanım alanlarınızda denemeler yaparak sizin için en uygun olanı bulabilirsiniz.

  • Bu modelin güvenliği açısından belirtmek istediğimiz bir diğer nokta da Gemini Nano'nun aslında kod çalıştırmadığıdır.

  • Yani Gemini Nano yalnızca bir soru soruyor ancak uygulamanız kodu tanımlıyor ve ardından kodu çalıştırıyor, bu bilgileri alıyor ve Gemini Nano'ya ne aktarılacağını belirliyor.

  • Böylece, Gemini Nano'ya hangi verileri göndereceğiniz konusunda daha fazla kontrol sahibi olursunuz. Ayrıca kullanıcılarınızın gizliliğini ve güvenliğini daha iyi koruyabilirsiniz.

Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama

  • David Chou

    Şimdi son adıma geçelim.

  • Yani ihtiyacımız olan tüm bilgilere sahibiz.

  • Karşılaştırmaları yapmak ve ardından bu bilgileri kullanıcıya anlaşılır bir şekilde döndürmek için Prompt API, yapılandırılmış çıkış ve konuşma sentezini kullanacağız.

  • Temel olarak, bu paketin gerçekten kullanıcıdan gelen bilgilerle aynı olup olmadığını, yani gerçek sipariş olup olmadığını bilmek istiyoruz.

  • Bunu yapmak için sesli mesajdaki bilgiler ve metin üzerinde yerleştirme oluşturacağız ve bunu daha önce yaptığımız yerleştirme vektörüne dönüştüreceğiz.

    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)
  • Ardından, matchScore kullanarak kosinüs benzerliği aracılığıyla kasadan aldığımız eşleşme ile arayanın bilgileri arasında bir karşılaştırma yapabiliriz.

  • Gördüğünüz gibi, resimler eşleşmese bile metin yine de çok faydalı.

  • Çok formatlı yerleştirmeler bu noktada oldukça faydalıdır.

  • Farklı bilgi parçalarını bir araya getirip ortak bir anlam çıkarabilirsiniz, değil mi?

  • Çok formatlı yerleştirme sayesinde tüm bunları aynı vektör uzayına eşleyebilirsiniz.

  • Düşünme sürecimizi iyileştirmek için burada da düşünme modunu kullanacağız.

    val forensicRequest = generateContentRequest(
        securitySystemInstruction,
        content {
            image(callerParcelPhotoBmp)
            image(orderEmailLensBmp)
            text(forensicAuditPrompt)
        }
    ) {
    
        // Enable Gemini Nano 4 reasoning before it answers
        enableThinking = true
    
    }
  • Ardından bir çıkış sınıfı tanımlayacağız.

    @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,
    )
  • Burada yaptığımız tek şey bir VisualContradiction oluşturmak ve karşılaşabileceğimiz farklı çelişki türleri için birkaç farklı enum oluşturmak, değil mi?

  • DUTY_FRAUD, PACKAGING_MISMATCH veya STATUS_CONFLICT fark etmez.

  • Ardından burada bir açıklama göreceğiz.

  • Ardından tüm bu bilgileri denetim kararına aktaracağız, değil mi?

    @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,
    )
  • Gördüğünüz gibi, bu çok basit bir işlem.

  • Sadece bu dolandırıcılık mı değil mi diye soruyoruz.

  • 0 ile 100 arasında hem minimum hem de maksimum değer belirleyebileceğiniz bir fraudRiskScore olabilir.

  • Zaman içinde takip etmek isterseniz kullanıcıya ne kadar para tasarrufu sağladığınızı da bildirebilirsiniz.

  • Ardından, az önce tanımladığımız görsel çelişkileri de listeleyebilirsiniz.

  • Burada dikkat edilmesi gereken bir diğer nokta da @Guide ek açıklamasının aslında isteğe bağlı olmasıdır.

  • Bu özelliği ayarlamanız ve bu tür bir açıklama sağlamanız gerekmez.

  • Bunun faydalı olmasının nedeni, yalnızca değişken tanımına bakarak neler olduğunu açıklayamayabilmenizdir.

  • Bu açıklama, Gemini Nano'nun hangi işlevleri çağıracağını ve doğru bilgileri doğru alanlara girecek şekilde nasıl ayarlayacağını anlamasına yardımcı olabilir.

  • Bu ayarı yaptıktan sonra her şey çok kolay.

  • Burada yalnızca üç satır kod çalıştırırsınız.

    val typedRequest = generateTypedContentRequest(forensicRequest, PhishingAuditVerdict::class)
    val response = generativeModel.generateContent(typedRequest)
    val verdict: PhishingAuditVerdict? = response.candidates.first().response
  • generateTypedContentRequest, bu içeriği oluşturursunuz ve ardından bu yanıtı alabilirsiniz.

  • Burada da nasıl görüneceğini gösteren bir örnek var.

    {
      "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."
    }
  • Böylece bunun bir dolandırıcılık olduğunu doğrulamış oluyoruz. Çok yüksek bir sahtekarlık riski puanı alıyoruz. Bu puan, arayan bilgileriyle karşılaştırdığınızda aldığınız verilerde açıkça görülen bu çelişkileri açıklıyor.

  • Ayrıca bu kullanıcının belirli bir miktar para tasarrufu yapmasını sağladık. Bu nedenle, buraya bir izleyici yerleştirebiliriz.

  • Evet.

  • Bu, ihtiyacımız olan tüm bilgileri kapsar.

  • Son adım ise bunu kullanıcıya erişilebilir bir şekilde göstermek.

  • Örneğin, kullanıcı hareket halindeyse veya görme engelliyse metni okumak zorunda kalmaması için bilgileri sesli olarak okuyabilirsiniz.

  • Bu nedenle, nasıl görüneceğini göstermek için metin okuma özelliğini uygulayacağız.

    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
    }
  • Yine çok basit bir şekilde yerel ayar ve dil belirtiyoruz. Ardından, @Generable ve @Guide kullanarak oluşturduğumuz kararı iletiyoruz.

  • Bunu yaptıktan sonra neredeyse anında sonuç alıyoruz.

    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
    }
  • Çoğu cihazda bu yanıtın oluşturulması 120 milisaniyeden kısa sürer.

  • Gerekli olan tüm hafızayı temizleriz.

  • Gerekirse platform API'lerini kullanarak yedek de ayarlayabiliriz.

    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 ...
  • Bu durumda ses yine robotik olur ancak en azından okuma bilmeyen kullanıcıların etkileşim kurabileceği bir şey sunulur.

Özet ve kapanış konuşması

  • David Chou

    Anladım.

  • Çok fazla bilgi paylaştık. Özetlemek gerekirse, giriş, önceliklendirme, alma ve denetleme süreçlerinde tüm bu farklı API'lerden yararlandık.

  • Örneğin, ses tanıma ve çeviri API'leri, Prompt API ve işlev çağrısı, yerleştirmeler ve AppSearch'ün yanı sıra yapılandırılmış çıkış ve konuşma sentezi kullanabilirsiniz.

  • Bu, tüm bu özellikleri tutarlı bir şekilde nasıl kullanabileceğinize ve tamamen cihaz üzerinde nasıl çalıştırabileceğinize dair bir örnektir.

  • Başta konuştuğumuz konuya dönecek olursak bu testi yapmanın önemli avantajları var, değil mi?

  • Bunu bugün cihazınızda çalıştırırsanız çoğu donanımda cihaz üzerinde önemli ölçüde daha hızlı olduğunu görürsünüz.

  • Bunun için para ödemeniz gerekmez ve sürekli çalıştırırsanız yüz binlerce dolar tasarruf edebilirsiniz.

  • Kullanıcı dağda veya bodrum katında olsa ve internete erişimi olmasa bile bu özellik kullanılabilir.

  • Buluta bilgi göndermeniz gerekmediği için tamamen gizlidir.

  • Tüm bu özellikler hakkında daha fazla bilgi edinmek için belgelere göz atabilir, QR kodunu tarayabilir veya bağlantıyı ziyaret edebilirsiniz.

  • Bunlar, yaklaşık son altı ay içinde kullanıma sunduğumuz tüm özelliklerdir.

  • Zaman ayırıp bu konuşmaya katıldığınız için hepinize çok teşekkür ederiz.

  • Bize zaman ayırdığınız için teşekkür ederiz.

  • Çok teşekkür ederim.

Kod

Kod snippet'leri

"End-to-end scam detection walkthrough" (Uçtan uca dolandırıcılık tespiti adım adım açıklamalı kılavuzu) videosunda 12:38'deki Kotlin snippet'i
val translator = SpeechTranslation.getClient(
    SpeechTranslatorOptions.Builder(
        Locale.JAPANESE, Locale.US
    ).build()
)
"Uçtan uca dolandırıcılık tespiti adım adım açıklamalı kılavuzu"nda 12:47'deki Kotlin snippet'i
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
    }
}
"Uçtan uca dolandırıcılık tespiti adım adım açıklaması" videosunda 13:13'teki Kotlin snippet'i
val recognizer = SpeechRecognition.getClient(
    speechRecognizerOptions {
        locale = Locale.US
        preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
    }
)
"Uçtan uca dolandırıcılık tespiti adım adım açıklaması"nda 13:24'teki Kotlin snippet'i
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)
    }
}
"Prompt API ve işlev çağrısı kullanarak girişleri önceliklendirme" videosunda 14:03'teki Kotlin snippet'i
retrieveOrderAndDeliveryPhotos(
  searchQuery = "camera lens customs",
  claimedFeeUsd = 45,
  comparePackagePhotos = true
)
"Prompt API ve işlev çağrısı kullanarak girişleri önceliklendirme" videosunda 14:07'deki Kotlin snippet'i
// 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()
)
"Triaging inputs using Prompt API and function calling" (Prompt API ve işlev çağrısı kullanarak girişleri önceliklendirme) videosunda 14:33'teki Kotlin snippet'i
// 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 */ }
"Triaging inputs using Prompt API and function calling" (Prompt API ve işlev çağrısı kullanarak girişleri önceliklendirme) videosunda 15:04'teki Kotlin snippet'i
// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {

}
"Prompt API ve işlev çağrısı kullanarak girişleri önceliklendirme" videosunda 15:15'teki Kotlin snippet'i
// 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) }
}
"Prompt API ve işlev çağrısı kullanarak girişleri ayırma" videosunda 15:35'teki Kotlin snippet'i
val vaultTools = AppFunctionTools.getClient().getAppFunctions()
val turn1Request = generateContentRequest(securitySystemInstruction, TextPart(turn1Prompt)) {

    enableThinking = true
    setTools(vaultTools)

}
val call = generativeModel.generateContent(turn1Request)
    .candidates.first().functionCalls.first()
"Retrieving order data with embeddings and AppSearch" videosunda 17:03'teki Kotlin snippet'i
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
"Retrieving order data with embeddings and AppSearch" videosunda 17:16'daki Kotlin snippet'i
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()
"Retrieving order data with embeddings and AppSearch" videosunda 17:48'deki Kotlin snippet'i
suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
    val qVec = embedModel.generateEmbedding(
        EmbeddingRequest(
            content = Content(listOf(TextPart(searchQuery)))
        )
    ).embedding.vector

}
"Retrieving order data with embeddings and AppSearch" videosunda 18:03'teki Kotlin snippet'i
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()

}
"Retrieving order data with embeddings and AppSearch" (Yerleştirmeler ve AppSearch ile sipariş verilerini alma) videosunda 18:12'deki Kotlin snippet'i
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() }
}
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) videosunda 19:24'teki Kotlin snippet'i
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)
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 20:10'daki Kotlin snippet'i
val forensicRequest = generateContentRequest(
    securitySystemInstruction,
    content {
        image(callerParcelPhotoBmp)
        image(orderEmailLensBmp)
        text(forensicAuditPrompt)
    }
) {

    // Enable Gemini Nano 4 reasoning before it answers
    enableThinking = true

}
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 20:15'teki Kotlin snippet'i
@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,
)
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 20:35'teki Kotlin snippet'i
@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,
)
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 21:35'teki Kotlin snippet'i
val typedRequest = generateTypedContentRequest(forensicRequest, PhishingAuditVerdict::class)
val response = generativeModel.generateContent(typedRequest)
val verdict: PhishingAuditVerdict? = response.candidates.first().response
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 21:44'teki JSON snippet'i
{
  "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."
}
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 22:22'deki Kotlin snippet'i
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
}
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 22:38'deki Kotlin snippet'i
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
}
"Comparing order details and defining audit verdicts" (Sipariş ayrıntılarını karşılaştırma ve denetim kararlarını tanımlama) bölümünde 22:49'daki Kotlin snippet'i
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 ...