در این جلسه، موارد استفاده مختلف هوش مصنوعی زایا را بااستفاده از میاناهای برنامهسازی کاربردی ML Kit کاوش خواهیم کرد تا متوجه شویم چه زمانی هوش مصنوعی زایای دروندستگاهی برای یک برنامه ایدهآل است و همچنین نحوه ارزیابی موارد استفاده موجود هوش مصنوعی زایا را درک کنیم تا ببینیم آیا مدلهای دروندستگاهی میتوانند سطح کیفیت آنها را برآورده کنند یا نه.
آوانویسی
دیوید چو
سلام.
بعدازظهر همگی بهخیر.
نام من دیوید چو است.
من مدیر محصول Android AICore هستم.
بسیار هیجانزده هستم که درباره قابلیتهای هوش مصنوعی دروندستگاهیمان با شما صحبت کنم.
امروز درباره چند حوزه اصلی صحبت خواهیم کرد.
اولین مورد این است که چرا باید از هوش مصنوعی دروندستگاهی استفاده کنید، بهویژه با توجه به شتاب پیشرفت هوش مصنوعی در فضای ابری.
دوم اینکه به برخیاز بهبودهایی که در سال گذشته ایجاد کردهایم فکر کنید تا این مدلهای هوش مصنوعی دروندستگاهی را بسیار قدرتمند کنیم.
و درنهایت، آخرین چیزی که درباره آن صحبت خواهیم کرد یک مثال سرتاسری است که نشان میدهد چگونه میتوانید بااستفاده از هوش مصنوعی دروندستگاهی از همه این مزایا بهره ببرید.
بنابراین، اول از همه، چرا باید از هوش مصنوعی دروندستگاهی استفاده کرد؟
درواقع ۴ دلیل اصلی وجود دارد.
اولی سرعت، دومی هزینه، سومی دردسترس بودن، و چهارمی حریم خصوصی است.
بنابراین، وقتی صحبت از سرعت میشود، درواقع میتوانید ببینید که استنتاج هوش مصنوعی دروندستگاهی اکنون به نقطهای میرسد که میتواند سریعتر از اجرا در فضای ابری باشد، صرفاً به این دلیل که مجبور نیستید اطلاعات را بین دستگاه کاربر و فضای ابری ردوبدل کنید.
بنابراین، در بسیاری از موارد استفادهای که آزمایش کردهایم، درواقع شاهد افزایش سرعت ۲ برابری در مقایسه هوش مصنوعی دروندستگاهی با فضای ابری هستیم.
مورد دوم فکر کردن درباره هزینه است.
بنابراین، بیایید تصور کنیم که میخواهید چیزی را با یک میلیون استنتاج در روز اجرا کنید.
اگر هوش مصنوعی را بهجای اجرا در فضای ابری، در دستگاه اجرا کنید، بهویژه در دستگاه کاربر، میتوانید صدها هزار دلار صرفهجویی کنید.
و سومین مورد، دردسترس بودن آفلاین است.
بنابراین، اوایل امسال نظرسنجیای ارسال کردیم و ۶۲٪ از توسعهدهندگانی که با آنها صحبت کردیم گفتند دردسترس بودن آفلاین یکی از سه دلیل اصلی برای استفاده از هوش مصنوعی دروندستگاهی است.
بنابراین، چه کاربر شما در ارتفاع ۳۵٬۰۰۰ پایی در هوا باشد یا در زیرزمین ایستگاه توکیو باشد و به اینترنت دسترسی نداشته باشد، همچنان میتواند از همه این قابلیتها بهره ببرد.
و درنهایت، حریم خصوصی یکی از بزرگترین دلایلی بود که هنگام نظرسنجی از توسعهدهندگان دیدیم.
بنابراین، ۷۲٪ آن را بهعنوان یکی از سه دلیل برتر رتبهبندی کردند.
٪۴۰ آن را بهعنوان شماره یک رتبهبندی کردند.
بنابراین وقتی صحبت از پیامها، یادداشتهای صوتی، عکسها، یا حتی اطلاعات مالی یا پزشکی میشود، کاربران میخواهند این اطلاعات خصوصی بماند و بنابراین اجرای استنتاج در دستگاه برای آنها بسیار ارزشمند خواهد بود.
بنابراین با توجه به همه این مزایا، ما ML Kit را ساختیم تا بتوانید همه این قابلیتها را حفظ کنید و از آنها بهره ببرید.
تلاش میکنیم این کار را بسیار ساده کنیم.
بنابراین، اگر شکایتی دارید، لطفاً با من تماس بگیرید.
در پایان این صحبت، اطلاعات تماسم را میگذارم.
مایلیم بدانیم چگونه میتوانیم موارد استفاده شما را فعال کنیم.
و بنابراین برای شروع، ما یک سری قابلیتها را برای مواردی مانند گفتار و استفاده خلاقانه فعال کردهایم.
بنابراین، مانند نمونهخوانی، بازنویسی، و همچنین فعال کردن پیاموارهدهی چندحالته انعطافپذیر.
بنابراین، هدف ما این است که سعی کنیم این را تا حد امکان قابلاستفاده کنیم.
و به همین دلیل است که بسیاری از کاربران ما، بسیاری از توسعهدهندگان ما، قبلاً شروع به پذیرش این موضوع کردهاند.
درحالحاضر ۹ برنامه طرف سوم داریم که ۱۰ میلیون بارگیری دارند و از این ویژگیهای هوش مصنوعی زایا در تولید استفاده میکنند.
و شاهد مقیاسبندی بسیار بیشتری در برنامههای طرف اول و برنامههای طرف سوم خود هستیم و رشد ۱۱۰ درصدی فصلبهفصل در راهاندازی برنامهها برای ویژگیهای تولید هوش مصنوعی را تجربه میکنیم.
بنابراین، در پیامرسانی، خلاقیت، و امور مالی، شما را تشویق میکنیم که این میاناهای برنامهسازی کاربردی را که دردسترس داریم امتحان کنید.
ما فکر میکنیم که احتمالات بیپایان هستند و شما احتمالاً در بسیاری از این موارد استفاده، تخیل بهتری نسبت به ما دارید، بنابراین دوست داریم با هم شریک شویم و بفهمیم که چگونه میتوانیم با هم کار کنیم.
بنابراین اکنون مزایای سطح بالا را درک میکنید.
بنابراین ممکن است سؤالاتی بپرسید، مثلاً، خب، دسترسی واقعاً چگونه است؟
عملکرد در دستگاه چگونه است؟
وقتی درباره سرعت صحبت میکنیم، واقعاً چقدر خوب است؟
یا مثلاً کیفیت خود مدل، درست است؟
بنابراین این موارد را یکییکی بررسی میکنیم.
ازنظر دسترسی، ما درحالحاضر در ۱۷۵ میلیون دستگاه AICore دردسترس هستیم.
انتظار داریم این شبکه در سال آینده بهطور قابلتوجهی گسترش یابد و صدها میلیون دستگاه دیگر را به آن اضافه کنیم.
بخشی از چیزی که این را ممکن میسازد این است که ما یک مهار بسیار ساده ساختهایم.
بنابراین، ML Kit API تنها چیزی است که باید در آن ادغام کنید.
پیچیدگیهای زیادی در AICore وجود دارد تا بتواند سیگنالهای ایمنی ورودی و خروجی را مدیریت کند، بارگیری مدل را بههمراه وزنها مدیریت کند، و نحوه راهاندازی آن را در زمان اجرا درنظر بگیرد.
بنابراین، هدف این است که ML Kit را بهگونهای بسازیم که استفاده از آن آسان باشد تا شما فقط نگران نحوه ارائه یک تجربه عالی باشید و ما نگران بقیه موارد باشیم.
دیوید چو
همچنین خوشحالیم که اعلام کنیم حافظه دسترسی تصادفی (RAM) موردنیاز برای اجرای Gemini Nano را بهطور قابلتوجهی کاهش دادهایم. Gemini Nano مدل اصلی است که بسیاری از این میاناهای برنامهسازی کاربردی هوش مصنوعی زایا را تقویت میکند.
بنابراین، این مقدار را از ۳٫۶ گیگابایت استفاده از حافظه دسترسی تصادفی به ۱٫۱ کاهش دادهایم.
بنابراین، واقعاً ۴ بهبود اصلی ایجاد کردهایم تا این امر ممکن شود.
اولین مورد استفاده از کوانتیزاسیون ۲ بیتی بهجای ۴ بیتی است.
بنابراین، نصف بیتها یعنی نصف بایتهایی که باید ازطریق مدل منتقل کنید.
روش دوم استفاده از حافظه پنهان KV مشترک است.
بنابراین، ما این مقدار را بهطور قابلتوجهی کاهش دادهایم و به ۲۰ لایه اول مدل اجازه دادهایم که این اطلاعات را در بسته ۳۵ مگابایتی همرسانی کنند، به این معنی که شما همچنان میتوانید به اندازه بافت ۱۶ هزار کلمه در این اندازه بسته بسیار بسیار کوچک دسترسی داشته باشید.
مورد سوم فعال کردن رمزگذارهای صوتی و تصویری تنبل است.
بنابراین، برای بسیاری از موارد استفاده از نوشتار که میبینیم، درواقع نیازی به بار کردن اینها از ابتدا ندارید.
و این کار نیم گیگابایت دیگر از حافظه دسترسی تصادفی را برای شما ذخیره میکند.
و درنهایت، برای بهبود عمر باتری، مقیاسبندی ساعت NPU را نیز فعال کردهایم.
همه اینها به این معنی است که NPU میتواند با سرعتی که برای صرفهجویی در مصرف برق لازم است کار کند و درعینحال مطمئن شود که همچنان کیفیت بالایی در موارد استفاده شما ارائه میدهد.
چند بهبود دیگر نیز درزمینه افزایش سرعت ایجاد کردهایم.
بنابراین، اولین مورد فعال کردن ذخیره پیشوند در حافظه نهان است.
میتوانید ببینید که در این مثال، پیامواره سیستم برای چند نوبت مجدداً استفاده میشود، درست است؟
و بنابراین پردازش این بسیار گران میشود.
اما با ذخیره پیشوند در حافظه نهان، درواقع میگوییم که لازم نیست آن پیامواره سیستم را بارها و بارها پردازش کنید، درست است؟
میتوانید آن را در حافظه نهان پیشوند ذخیره کنید، یعنی برای این مثال، ۶۰٪ از کدهای زبانی را که باید ارسال کنید ذخیره میکنید، که همچنین بهمعنای افزایش ۶۰٪ سرعت در هر دستگاهی است که استفاده میکنید.
بهبود دومی که ایجاد کردهایم در رمزگشایی گمانهزن است.
و در این مثال، میبینید که Gemini Nano در هر گذر یک توکن تولید میکند.
و این به این دلیل است که مدلهای زبانی بزرگ خودبازگشتی هستند، یعنی باید همچنان به نشانهای قبلی تولیدشده نگاه کنند تا نشانهای بعدی را تشخیص دهند.
بنابراین، روشی که ما برای مدیریت این موضوع استفاده کردیم، ساختن یک مدل پیشنویسساز بود که تعداد زیادی از توکنها را با سرعت بسیار بیشتری تولید میکند.
این مدل پیشنویسساز بهطور قابلتوجهی کوچکتر از Gemini Nano است و بنابراین میتواند تعداد زیادی از نشانها را بهطور همزمان تولید کند و آنها را به Gemini ارسال کند تا ببیند کدامیک واقعاً با حدسهای Gemini Nano مطابقت دارند.
و بنابراین در این مثال، میتوانید ببینید که در اولین گذر، شش توکن تولید میکند، و در گذر بعدی پنج توکن تولید میکند، و سپس، در نوبت سوم، درواقع کار تمام میشود.
And so what we've been seeing is that for a lot of the internal use cases we're looking at, this is leading to an on average 2x speedup.
بسته به سختافزارتان، ممکن است بهبودهای سریعتری دریافت کنید.
بسیارخوب.
بنابراین، این یکی از دلایلی است که، میدانید، اگر به سرعت برگردیم، واقعاً میتوانید این افزایش سرعت چشمگیر را ببینید، درست است، بین دستگاه و ابر، حتی در شبکه 5G.
میبینید که بارگذاری ساده آن دادهها در فضای ابری حتی برای ۱٬۰۰۰ تا ۱٬۳۰۰ کد ورودی فقط یک ثانیه طول میکشد، درست است؟ بنابراین، حتی اگر پیشپر کردن و رمزگشایی در فضای ابری سریعتر باشد، اجرای روی دستگاه همچنان بهطور قابلتوجهی سریعتر است.
و سپس، وقتی صحبت از کیفیت میشود، خوشحالیم که اعلام کنیم این مدل 2B درواقع درمقابل مدلهای 27B سال گذشته عملکرد خوبی دارد.
اگرچه عملکرد آن به اندازه مدلهای مرزشکنی که امروزه داریم نیست، اما آنچه در اینجا نشان میدهیم این است که وقتی صحبت از هوش مصنوعی در این مدلهای دروندستگاهی میشود، پیشرفت قابلتوجهی وجود دارد، درست است؟
و بنابراین میتوانید ببینید که برای MMLU-Pro، که یک محک دانش استاندارد است، مدل 2B تقریباً بهخوبی مدل پارامتر 27B از سال گذشته است.
و سپس وقتی نوبت به معیارهای علوم فارغالتحصیلی، استدلال، و استفاده از ابزار عاملی میرسد، این مدل 2B درواقع بهتر است، هرچند اندازه آن کمتر از یکدهم مدل سال گذشته است.
بنابراین انتظار داریم این روندها ادامه یابد.
ما همچنان به همکاری نزدیک با تیم GDM ادامه میدهیم تا مطمئن شویم که میتوانیم این بهبودها را در مدل ادامه دهیم تا شما بتوانید بهترین ارزش را از اینها دریافت کنید.
دیوید چو
بسیارخوب.
خب، من فقط تعداد زیادی از مزایا و بهبودهای جدید را به شما معرفی کردم، و شاید بپرسید، «واقعاً با این مدلها چه کاری میتوانیم انجام دهیم؟»
و بنابراین همه این بهبودهای قابلیتی را در میانای برنامهسازی کاربردی نیز ایجاد کردهایم.
بنابراین با دستورالعملهای سیستم، میتوانید یک شخصیت تنظیم کنید.
با ورودی چندتصویری، میتوانید چندین تصویر را بهطور همزمان بارگذاری کنید.
اوایل امسال، حالت تفکر را نیز فعال کردیم تا بتوانید زمان بیشتری را صرف مسائل استدلالی و همچنین فراخوانی ابزار کنید.
و درنهایت، با خروجی ساختاریافته، این امکان را فراهم کردهایم که بتوانید یک کلاس Kotlin را حاشیهنویسی کنید و شیئی را دریافت کنید که با طرحواره خاص شما مطابقت دارد.
علاوهبراین، سعی میکنیم از توسعهدهندگان درباره بهبودهای جدیدی که میخواهند ایجاد کنند سؤال کنیم.
بنابراین ۹۱٪ از توسعهدهندگانی که در نظرسنجی با آنها صحبت کردیم گفتند که اگر یکی از این قابلیتها دردسترس باشد، تجربهای را راهاندازی خواهند کرد.
بنابراین، اوایل این هفته، ما این چهار قابلیت را راهاندازی کردیم: ترجمه گفتار، جاسازیها، نوشتار به گفتار، و فراخوانی تابع.
با ترجمه گفتار، میتوانید صدا را از بیشاز ۲۰ زبان ترانویسی کنید.
با جاسازیها، میتوانید محتوای برنامهتان را براساس معنی معنایی جستجو کنید، نه فقط بااستفاده از جستجوی کلیدواژه.
با نوشتار به گفتار، اکنون میتوانید صداهای شبیه انسان را برای تجربههایتان ارائه دهید، بنابراین دیگر نیازی به صداهای رباتیک ندارید.
و درنهایت، با فراخوانی تابع، میتوانید به برنامهتان اجازه دهید ازجانب کاربران شما اقداماتی انجام دهد.
بنابراین، ما قبلاً با چند توسعهدهنده همکاری کردهایم تا ببینیم چگونه میتوانند عوامل درونبرنامه را در تجربههای خود بسازند و امیدواریم که اواخر سال آینده آن را راهاندازی کنیم.
دیوید چو
بنابراین با توجه به همه این قابلیتها، بیایید درواقع یک مثال سرتاسری را مرور کنیم که در آن با همه این قابلیتها میسازیم.
بنابراین، بیایید تصور کنیم که یک عکاس آمریکایی وجود دارد و او در ژاپن سفر میکند.
و همانطور که در عکس میبینید، مشخص است که دوربین یا لنز کافی ندارند.
بنابراین، لنز جدیدی سفارش میدهند و آن را به هتلشان ارسال میکنند.
اما مشکلی وجود دارد: همانطور که روزشان را میگذرانند، یک پیام صوتی دریافت میکنند و به آنها گفته میشود که برای دریافت بستهشان تا پایان روز، باید ۶۸۰۰ ین هزینه ترخیص پرداخت کنند.
همچنین پیامکی دریافت کردهاند که حاوی عکسی از بسته نگهداشتهشده است.
پس در این مثال، کاربر میخواهد نوعی محافظت تشخیص کلاهبرداری داشته باشد، درست است؟
آنها میخواهند بدانند چگونه میتوانند پیامهای صوتی را بهعنوان قانونی یا کلاهبرداری شناسایی کنند.
بنابراین، میخواهیم همه اطلاعات را خصوصی نگه داریم، میخواهیم مطمئن شویم که اطلاعات مستدل است، اما همچنین قابلتوضیح است.
بنابراین میخواهیم مطمئن شویم که آنها میتوانند نوشتار ژاپنی را درک کنند و همچنین بتوانند از هر قابلیت زبانی که دردسترس است درصورت داشتن نیازهای دسترسپذیری استفاده کنند.
بنابراین، چهار مرحله در اینجا وجود خواهد داشت، درست است؟
مرحله اول فقط پردازش آن پست صوتی در نوشتار است، بنابراین آن پست صوتی را ترجمه میکند.
برای این کار از Speech Translation API استفاده خواهیم کرد.
و سپس آنها میتوانند درصورت تمایل سؤالی بپرسند، بنابراین ما از Speech Recognition API استفاده خواهیم کرد.
و سپس، میخواهیم توابعی را تعریف کنیم که بتوانند اطلاعات لازم را برای کاربر بازیابی کنند تا به آنچه نیاز دارد برسد.
بنابراین، از Prompt API و فراخوانی تابع استفاده خواهیم کرد.
اکنون این در پسزمینه اجرا میشود و چیزی برای کاربر قابلمشاهده نیست.
و سپس سوم، ما در واقع بااستفاده از embedding API و AppSearch نحوه ذخیره و بازیابی آن اطلاعات را پیدا خواهیم کرد.
این برنامه در پسزمینه نیز اجرا خواهد شد.
و سپس، درنهایت، براساس اطلاعاتی که جمعآوری میکنیم، بااستفاده از Prompt API، خروجی ساختاریافته، و گفتارساخت، حکم صادر میکنیم.
بسیارخوب.
پس بیایید شروع کنیم.
میخواهیم از ترجمه گفتار و تشخیص گفتار استفاده کنیم.
بنابراین، اولین قدم درواقع فقط گرفتن آن پست صوتی و نوشتار و تبدیل آن به اطلاعات مفید است، درست است؟
و بنابراین، کاربر این پیام را دریافت کرده است: «سلام، باید این هزینه ۶۸۰۰ ینی را پرداخت کنید.
عوارض، شاید پیشپرداخت نشده باشد، بنابراین باید آن را پرداخت کنید.»
و بنابراین، آنها میخواهند بتوانند آن ترجمه را داشته باشند، درست است؟
بنابراین، یکی از چیزهایی که میخواهیم در اینجا به آن اشاره کنیم این است که هم ترجمه و هم سؤال کاربر درباره اطلاعات، همگی بااستفاده از یک کدبند صوتی به Gemini Nano 4 میروند، زیرا کدبند صوتی با بیشاز ۱۰۰ زبان آموزش دیده است.
و بنابراین میتوانید آن ترجمه و همچنین تشخیص خودکار گفتار را برای نوشتار سؤال کاربر دریافت کنید.
بنابراین، برای ترجمه، تنها کاری که باید انجام دهیم این است که زبان مبدأ و مقصد را تنظیم کنیم.
val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)در این مورد، از زبان ژاپنی و همچنین انگلیسی امریکایی استفاده خواهیم کرد.
میخواهیم یک جریان Kotlin راهاندازی کنیم که بایتها را مستقیماً به مترجم گفتار منتقل میکند.
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
}
}و سپس میتوانیم پاسخ را بااستفاده از نوشتار ترجمهشده جمعآوری کنیم.
بسیارخوب.
عالی.
بنابراین، اکنون متوجه میشویم که پست صوتی واقعاً چه میگوید، درست است؟
بسته لنز دوربین وجود دارد.
در گمرک نگه داشته شده است.
آنها باید آن کارمزد ۶۸۰۰ ینی را پرداخت کنند.
و اکنون کاربر میخواهد بتواند درباره آن سؤال بپرسد.
بسیارخوب.
بنابراین، آیا این توقیف گمرکی واقعاً واقعی است؟
در این مورد، از ASR API استفاده خواهیم کرد.
val recognizer = SpeechRecognition.getClient(
speechRecognizerOptions {
locale = Locale.US
preferredMode = SpeechRecognizerOptions.Mode.MODE_ADVANCED
}
)کاری که ما انجام میدهیم این است که همچنان انگلیسی امریکایی را بهعنوان زبان محلی تنظیم میکنیم.
و سپس میتوانیم آن را روی حالت پیشرفته تنظیم کنیم که از کدبند صوتی بهره میبرد.
و سپس، میتوانیم آن اطلاعات را نیز ارسال کنیم، پاسخ نهایی را جمعآوری کنیم، و سپس آن را به اطلاعاتی تبدیل کنیم که به 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)
}
}بسیارخوب.
خب، اکنون بخش اول پذیرش را تمام کردیم.
همانطور که میبینید، به خطوط کد زیادی نیاز نداشت.
دیوید چو
بنابراین بخش دوم تریاژ است، یعنی فکر کردن درباره نحوه استفاده از Prompt API و فراخوانی تابع برای تشخیص اینکه چه نوع اطلاعاتی موردنیاز است.
بسیارخوب.
بنابراین، برای ساختن معماری این، ابتدا چند دستورالعمل سیستم را راهاندازی میکنیم.
و سپس پست صوتی، پیامک، و عکس، و همچنین سؤال کاربر را بهعنوان ورودی به Gemini Nano ارسال میکنیم.
و سپس توابعی را تعریف میکنیم که درواقع اطلاعات موردنیاز ما را بازیابی میکنند.
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()
)در اینجا میتوانید نمونه بسیار سادهای از نحوه راهاندازی حسابرس امنیتی را ببینید، آنچه میگوید این است که ما بستهبندی و برچسبها را بررسی متقابل میکنیم، به هزینههای سفارشی نگاه میکنیم، و همچنین هرگونه درخواست پرداخت ضروری را پرچمگذاری میکنیم.
ممکن است متوجه شوید که بهینهسازی پیامواره در اینجا واقعاً مفید خواهد بود تا کیفیت بهطور قابلتوجهی بهتر شود.
و سپس وقتی آن مجموعه دستورالعمل سیستم را داشته باشیم، میتوانیم همه محتوایی را که از مرحله قبل داشتیم، ترجمه هم پست صوتی و هم نوشتار، و همچنین سؤال کاربر را بگیریم و همه را به این فیلد TextPart منتقل کنیم، همراه با دستورالعملهای سیستم برای دریافت آن اطلاعات.
// 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 */ }و سپس میخواهیم تابعی را تعریف کنیم که بتوانیم از آن استفاده کنیم.
بنابراین، برای این مثال خاص، خوشحالیم که اعلام کنیم درواقع با عملکرد برنامه ادغام شدهایم.
بنابراین، اگر هرکدام از شما با آن آشنایی دارید، احتمالاً ازقبل تقریباً میدانید که این چگونه کار میکند.
بنابراین در اینجا یک نقطه ورودی AppFunctionService راهاندازی میکنیم و سپس یک تابع برنامه تعریف میکنیم.
// Registered in AndroidManifest.xml
@AppFunctionServiceEntryPoint("ScamInvestigationService", "scam_tools")
abstract class ScamInvestigationTools : AppFunctionService() {
}میبینید که این دقیقاً همان چیزی است که اگر درحال راهاندازی کارکردهای برنامه برای عامل سیستم بودید، انجام میدادید.
شما پارامترهای searchQuery، هزینه ادعا، و همچنین اینکه آیا باید عکسهای بسته را مقایسه کنیم یا نه را بهعنوان چند نمونه پارامتر تنظیم میکنید، درست است؟
// 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) }
}ممکن است شما موارد دیگری را تعریف کنید.
و سپس، درنهایت، برای آن تابع، تابع executeVaultSearch را داریم که بعداً تعریف خواهیم کرد.
یکی از چیزهایی که میخواهم در اینجا به آن اشاره کنم این است که حالت تفکر را دردسترس داریم.
val vaultTools = AppFunctionTools.getClient().getAppFunctions()
val turn1Request = generateContentRequest(securitySystemInstruction, TextPart(turn1Prompt)) {
enableThinking = true
setTools(vaultTools)
}
val call = generativeModel.generateContent(turn1Request)
.candidates.first().functionCalls.first()اکیداً توصیه میکنم هنگام اجرای فراخوانی ابزار از آن استفاده کنید.
دلیل آن این است که ما آزمایشهای زیادی انجام دادهایم که نشان میدهد هنگام اجرای فراخوانی ابزار در Gemini Nano 4، دقت ۱۳ امتیاز از ۱۰۰ امتیاز بهبود مییابد.
و بنابراین، حتی در برخی موارد درواقع بهطور قابلتوجهی بالاتر است، و بنابراین این ممکن است نتایج بسیار بهتری به شما بدهد.
و اگر میخواهید درباره فراخوانی تابع بیشتر بدانید، لطفاً در جلسه همکارانم، یولاندا و ویکتوریا، که بعداً برگزار میشود شرکت کنید.
این درباره قابلیتهای عاملگرا در برنامه شما است.
فکر میکنم ویکتوریا جایی در میان جمعیت نشسته باشد، پس بله، حتماً به او هم سلام کنید.
دیوید چو
بسیارخوب، عالی است.
بنابراین، اکنون دو مرحله اول را بهپایان رساندهایم.
بیایید درباره نحوه عملکرد بازیابی صحبت کنیم.
بنابراین، از جاسازیها و AppSearch استفاده خواهیم کرد.
بنابراین، شاید چند قدم به عقب برگردیم، درست است، در سفر کاربر، در نقطهای که لنز را سفارش داده است، آنچه میخواهیم انجام دهیم درواقع نمایهسازی آن اطلاعات است تا هم برای کاربر و هم برای توسعهدهنده قابلدسترسی باشد، درست است؟
بنابراین، در این مورد اطلاعاتی درباره شماره بسته سفارش داریم.
شماره رهگیری هم دارد.
این پیام حاوی جزئیاتی درباره مقصد ارسال است.
و میتوانید ببینید که در اینجا ذکر شده است که عوارض ازقبل پرداخت شده است.
بنابراین، کاری که انجام میدهیم این است که یک جاسازی چندحالتی تولید میکنیم تا آن اطلاعات را همراه با هر اطلاعات دیگری که ممکن است برای ما مهم باشد، نمایهسازی کنیم.
بنابراین، این شما و این AgentSpace.
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ایمیل سفارش را دریافت میکنیم.
بازهم بسیار ساده است، ما فقط یک تصویر و یک نوشتار را ارسال میکنیم، اما ممکن است محتوای بیشتری داشته باشید که بخواهید در اینجا نمایه کنید.
و سپس، بردار جاسازی را تولید میکنیم.
بنابراین، پساز انجام این کار، از AppSearch برای ایجاد سند استفاده میکنیم و سپس هر ویژگیای را که فکر میکنیم برای تولید آن سند خاص لازم است تنظیم میکنیم.
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()و سپس، نکتهای که باید در اینجا به آن توجه کرد این است که دلیل اینکه میخواهیم جاسازیهای چندحالته را انجام دهیم، درست است، زیرا ممکن است هیچ کلمه مشترکی در اینجا وجود نداشته باشد، اما چون تصاویر و نوشتار درباره محتوای مشابهی هستند، این به ما کمک میکند تا درواقع شباهت معنایی را بهدست آوریم.
بنابراین، میتوانیم از شباهت کسینوسی برای پیدا کردن مواردی که هیچ کلمه مشترکی ندارند استفاده کنیم.
بسیارخوب، این تابع executeVaultSearch است که قبلاً درباره آن صحبت کرده بودیم.
suspend fun executeVaultSearch(searchQuery: String): VaultMatch {
val qVec = embedModel.generateEmbedding(
EmbeddingRequest(
content = Content(listOf(TextPart(searchQuery)))
)
).embedding.vector
}این همان چیزی است که توسط تابع برنامهای که تعریف کرده بودیم فراخوانی میشود.
بنابراین، در این مثال، پرسمان جستجویی را که توسط Gemini Nano تولید شده است ارسال میکنیم.
و سپس، از یک استراتژی رتبهبندی، شباهت کسینوسی، استفاده خواهیم کرد.
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 فقط به ما کمک میکند تا آن اطلاعات را دریافت کنیم.
و سپس، در نهایت، آستانه ۰٫۶۸ را مشخص میکنیم تا تعیین کنیم حداقل شباهت کسینوسی برای واکشی اطلاعات چقدر است.
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() }
}بنابراین، این چیزی است که شاید بخواهید با آن بازی کنید تا درحین اجرای موارد استفاده خودتان ببینید چه چیزی واقعاً برای شما کار میکند.
و سپس، یک چیز که میخواهیم در اینجا به آن اشاره کنیم این است که وقتی به امنیت این مدل فکر میکنید، Gemini Nano درواقع مدلی نیست که کد را اجرا میکند، درست است؟
بنابراین، Gemini Nano فقط یک سؤال میپرسد، اما برنامه شما کد را تعریف میکند و سپس درواقع همان برنامهای است که کد را اجرا میکند، آن اطلاعات را بازیابی میکند، و سپس تعیین میکند چه چیزی به Gemini Nano منتقل شود.
بنابراین، این ویژگی به شما کنترل بیشتری بر آنچه میخواهید دراختیار Gemini Nano قرار دهید میدهد و همچنین به تضمین حریم خصوصی و امنیت بیشتر برای کاربران شما کمک میکند.
دیوید چو
بسیار خوب، و اکنون این مرحله نهایی است.
بنابراین، همه اطلاعات موردنیاز را داریم.
ما از Prompt API، خروجی ساختاریافته، و ترکیب گفتار برای انجام مقایسهها استفاده خواهیم کرد و سپس آن اطلاعات را بهشیوهای قابلفهم به کاربر برمیگردانیم.
بسیار خوب، پس اساساً، درست است، آنچه میخواهیم بدانیم این است که آیا این بسته واقعاً همان اطلاعات کاربر است که سفارش واقعی است.
بنابراین، برای انجام این کار، ما همچنین جاسازی را روی آن اطلاعات از پست صوتی و همچنین متن تولید خواهیم کرد و آن را به همان بردار جاسازی که قبلاً انجام دادیم تبدیل خواهیم کرد.
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)و سپس، از matchScore استفاده میکنیم، بااستفاده از شباهت کسینوسی، میتوانیم مقایسهای بین آن تطابق که از گاوصندوق بازیابی کرده بودیم و همچنین اطلاعات آن تماسگیرنده انجام دهیم.
و بنابراین، آنچه در اینجا میبینید این است که حتی اگر تصاویر مطابقت نداشته باشند، نوشتار همچنان واقعاً مفید است.
بنابراین، اینجا است که جاسازیهای چندحالته واقعاً بسیار ارزشمند هستند.
میتوانید بخشهای مختلف اطلاعات را وارد کنید و سپس به یک معنای معنایی مشترک برسید، درست است؟
بنابراین، جاسازی چندحالتی باعث میشود که بتوانید همه آن را روی یک فضای برداری یکسان نگاشت کنید.
بازهم در اینجا از حالت اندیشگر برای بهبود تفکرمان استفاده میکنیم.
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,
)بنابراین، تنها کاری که در اینجا انجام میدهیم این است که یک VisualContradiction راهاندازی میکنیم و چند شمارش مختلف از انواع مختلف تناقضات احتمالی که ممکن است داشته باشیم راهاندازی میکنیم، درست است؟
چه DUTY_FRAUD باشد، چه PACKAGING_MISMATCH، یا STATUS_CONFLICT.
و سپس در اینجا توضیحی خواهیم داشت.
و سپس همه این اطلاعات را به حکم ممیزی منتقل میکنیم، درست است؟
@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,
)بنابراین میتوانید در اینجا ببینید که خیلی ساده است.
All we're saying is this a scam or not?
شاید fraudRiskScore داشته باشیم که در آن میتوانید هم مقدار حداقل و هم مقدار حداکثر را از ۰ تا ۱۰۰ تنظیم کنید.
حتی میتوانید به کاربر اطلاع دهید که چقدر پول برای او پسانداز کردهاید، اگر میخواهید این موضوع را درطول زمان پیگیری کنید.
و سپس میتوانید هر تضاد بصری را که همینالان تعریف کردیم فهرست کنید.
یکی دیگر از چیزهایی که باید در اینجا به آن توجه کرد این است که حاشیهنویسی @Guide درواقع اختیاری است.
لازم نیست حتماً آن را تنظیم کنید و چنین شرحی ارائه دهید.
دلیل مفید بودن آن این است که ممکن است نتوانید آنچه را که درحال رخ دادن است فقط با تعریف متغیر توضیح دهید.
بنابراین، داشتن این شرح میتواند برای Gemini Nano مفید باشد تا بفهمد کدام توابع خاص را فراخوانی کند و چگونه آن را تنظیم کند تا اطلاعات درست را در فیلدهای درست وارد کند.
و بله، بعداز اینکه آن را راهاندازی کردید، بسیار ساده است.
فقط سه خط کد را اینجا اجرا میکنید.
val typedRequest = generateTypedContentRequest(forensicRequest, PhishingAuditVerdict::class)
val response = generativeModel.generateContent(typedRequest)
val verdict: PhishingAuditVerdict? = response.candidates.first().responsegenerateTypedContentRequest، شما آن محتوا را تولید میکنید و سپس میتوانید آن پاسخ را بازیابی کنید.
و سپس اینجا چیزی است که نشان میدهد چگونه خواهد بود، درست است؟
{
"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
}پس دوباره، خیلی ساده، یک منطقه زبانی و یک زبان را نیز مشخص میکنیم و سپس آن رأی را که بهتازگی بااستفاده از @Generable و @Guide تولید کرده بودیم ارسال میکنیم.
و بعداز اینکه آن را دریافت کردیم، درست است، تقریباً فوری است.
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 ...این گزینه همچنان صدای رباتیک دارد، اما حداقل چیزی به کاربر میدهد که اگر نتواند بخواند، با آن تعامل برقرار کند.
دیوید چو
بسیارخوب.
خب، این خیلی زیاد بود، اما برای خلاصه کردن آنچه انجام دادیم، درست است، ما از همه این APIهای مختلف در فرایندهای دریافت، تریاژ، بازیابی و ممیزی استفاده کردیم.
مثلاً بااستفاده از میاناهای برنامهسازی کاربردی تشخیص گفتار و ترجمه، میانای برنامهسازی کاربردی پیامواره و فراخوانی تابع، جاسازیها، و AppSearch، و همچنین خروجی ساختاریافته و ترکیب گفتار.
و این فقط یک نمونه از نحوه استفاده از همه این قابلیتها بهصورت منسجم و اجرای کامل آن در دستگاه است.
و سپس برگردیم به آنچه در ابتدا درباره آن صحبت کردیم، درست است، مزایای قابل توجهی برای اجرای این وجود دارد، درست است؟
اگر امروز واقعاً این را در دستگاهتان اجرا کنید، خواهید دید که این مدل در اکثر سختافزارها بهطور قابلتوجهی در دستگاه سریعتر است.
این کار هیچ هزینهای برای شما ندارد، درست است، و اگر همیشه این کار را انجام دهید، بهطور بالقوه صدها هزار دلار صرفهجویی میکنید.
و سپس این ویژگی دردسترس است، درست است، صرفنظر از اینکه کاربر در کوهستان باشد یا در زیرزمین و به اینترنت دسترسی نداشته باشد.
و کاملاً خصوصی است چون لازم نیست هیچ اطلاعاتی به فضای ابری ارسال کنید.
و با توجه به همه این قابلیتها، لطفاً با خیال راحت به اسناد نگاه کنید، به رمزینه پاسخسریع نگاهی بیندازید، یا به پیوند بروید.
اینها همه قابلیتهایی هستند که تقریباً در شش ماه گذشته راهاندازی کردهایم.
از همه شما بسیار سپاسگزاریم که برای شرکت در این گفتگو وقت گذاشتید.
از اینکه وقتتان را با ما گذراندید بسیار سپاسگزاریم.
بله، خیلی ممنون.
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 ...