এই সেশনে, আমরা ML Kit API ব্যবহার করে বিভিন্ন GenAI ব্যবহারের কেস এক্সপ্লোর করব যাতে বোঝা যায় যে কখন অন-ডিভাইস GenAI কোনও অ্যাপের জন্য আদর্শ এবং কীভাবে বিদ্যমান GenAI ব্যবহারের কেস মূল্যায়ন করা যায় যাতে দেখা যায় যে অন-ডিভাইস মডেলগুলি তাদের কোয়ালিটি বার পূরণ করতে পারে কিনা।
ট্রান্সস্ক্রিপ্ট করা
ডেভিড চৌ
হ্যালো।
শুভ অপরাহ্ন, সবাইকে।
আমার নাম ডেভিড চৌ।
আমি Android AICore-এর প্রোডাক্ট লিড।
আমাদের অন-ডিভাইস AI ক্ষমতা সম্পর্কে আপনার সাথে কথা বলতে পেরে খুবই আনন্দিত।
আজ আমরা কয়েকটি মূল বিষয় নিয়ে আলোচনা করব।
প্রথমত, ক্লাউডে AI-এর অগ্রগতির ত্বরণ বিবেচনা করে, কেন আপনার অন-ডিভাইস AI ব্যবহার করা উচিত তা নিয়ে ভাবুন।
দ্বিতীয়ত, গত এক বছরে আমরা যেসব উন্নতি করেছি, সেগুলি সম্পর্কে চিন্তা করা, যাতে এই অন-ডিভাইস AI মডেলগুলি অত্যন্ত শক্তিশালী হয়ে ওঠে।
আর সবশেষে, আমরা একটি এন্ড-টু-এন্ড উদাহরণ নিয়ে আলোচনা করব যা দেখাবে যে অন-ডিভাইস AI ব্যবহার করে আপনি কীভাবে এইসব সুবিধা কাজে লাগাতে পারবেন।
তাহলে, প্রথমত, অন-ডিভাইস AI কেন ব্যবহার করবেন?
এর প্রধানত ৪টি কারণ রয়েছে।
প্রথমটি হল স্পিড, দ্বিতীয়টি হল খরচ, তৃতীয়টি হল উপলভ্যতা এবং চতুর্থটি হল গোপনীয়তা।
তাই, স্পিডের কথা বলতে গেলে, আপনি আসলে দেখতে পাবেন যে অন-ডিভাইস AI ইনফারেন্স এখন এমন একটি পর্যায়ে পৌঁছে যাচ্ছে যেখানে এটি ক্লাউডে চালানোর চেয়ে দ্রুত হতে পারে কারণ আপনাকে ব্যবহারকারীর ডিভাইস এবং ক্লাউডের মধ্যে তথ্য আদান-প্রদান করতে হবে না।
তাই, আমরা পরীক্ষা করেছি এমন অনেক ব্যবহারের ক্ষেত্রে, আমরা আসলে অন-ডিভাইস AI বনাম ক্লাউডের তুলনা করার সময় ২ গুণ স্পিডআপ দেখতে পাচ্ছি।
দ্বিতীয়টি খরচের কথা ভাবছে।
এবং তাই, আসুন কল্পনা করি যে আপনি দিনে এক মিলিয়ন ইনফারেন্স কিছু চালাতে চান।
আপনি যদি অন-ডিভাইস AI চালান, বিশেষ করে ব্যবহারকারীর ডিভাইস থেকে, তাহলে ক্লাউডে চালানোর তুলনায় আপনি আসলে কয়েক লক্ষ ডলার সাশ্রয় করতে পারবেন।
এবং তৃতীয়টি হল অফলাইনে উপলভ্যতা।
তাই, আমরা এই বছরের শুরুতে একটি সমীক্ষা পাঠিয়েছিলাম এবং আমরা যে ডেভেলপারদের সাথে কথা বলছিলাম তাদের মধ্যে ৬২% বলেছেন যে অফলাইন উপলভ্যতা হল অন-ডিভাইস AI ব্যবহার করার সেরা তিনটি কারণের মধ্যে একটি।
তাই আপনার ব্যবহারকারী ৩৫,০০০ ফুট উপরে আকাশে থাকুন বা টোকিও স্টেশনের সাব-বেসমেন্টে থাকুন এবং ইন্টারনেট অ্যাক্সেস না থাকলেও, তিনি এইসব সুবিধা ব্যবহার করতে পারবেন।
এবং অবশেষে, ডেভেলপারদের উপর সমীক্ষা করার সময় আমরা দেখেছি যে গোপনীয়তা হল অন্যতম বড় কারণ।
তাই, ৭২% এটিকে সেরা তিনটি কারণের মধ্যে একটি হিসেবে র্যাঙ্ক করেছেন।
৪০% এটিকে এক নম্বর হিসেবে র্যাঙ্ক করেছেন।
তাই আপনার মেসেজ, ভয়েস নোট, ফটো বা এমনকি আর্থিক বা মেডিকেল তথ্যের ক্ষেত্রে, ব্যবহারকারীরা এগুলি ব্যক্তিগত রাখতে চান এবং তাই অন-ডিভাইস ইনফারেন্স রান করা তাদের জন্য সত্যিই মূল্যবান হতে চলেছে।
সুতরাং, এই সমস্ত সুবিধা দেওয়া হয়েছে, তাই না, আমরা ML Kit তৈরি করেছি যাতে আপনি এই সমস্ত ক্ষমতা বজায় রাখতে এবং ব্যবহার করতে পারেন।
আমরা এটিকে অত্যন্ত সহজ করার চেষ্টা করছি।
তাই, আপনার কোনও অভিযোগ থাকলে, আমাকে জানাতে দ্বিধা করবেন না।
এই কথোপকথনের শেষে আমি আমার পরিচিতির তথ্য দিয়ে দেব।
আপনার ব্যবহারের ক্ষেত্রে আমরা কীভাবে সাহায্য করতে পারি তা জানতে পারলে ভালো হয়।
তাই শুরু করার জন্য, আমরা স্পিচ এবং ক্রিয়েটিভ ব্যবহারের মতো একগুচ্ছ ক্ষমতা চালু করেছি।
যেমন, প্রুফরিডিং, আবার লেখা এবং ফ্লেক্সিবল মাল্টিমোডাল প্রম্পটিং চালু করা।
তাই, আমাদের লক্ষ্য হল এটিকে যতটা সম্ভব ব্যবহারযোগ্য করে তোলা।
এই কারণেই আমাদের অনেক ব্যবহারকারী এবং ডেভেলপার ইতিমধ্যেই এটি গ্রহণ করতে শুরু করেছেন।
আমাদের কাছে ইতিমধ্যেই ৯টি থার্ড-পার্টি অ্যাপ রয়েছে যেগুলি ১০ মিলিয়ন বার ডাউনলোড করা হয়েছে এবং প্রোডাকশনে এই GenAI ফিচারগুলি ব্যবহার করছে।
আমাদের ফার্স্ট-পার্টি ও থার্ড-পার্টি অ্যাপের ক্ষেত্রে আমরা আরও অনেক বেশি স্কেল আপ দেখতে পাচ্ছি এবং AI প্রোডাকশন ফিচারের জন্য অ্যাপ লঞ্চের ক্ষেত্রে ১১০% QoQ বৃদ্ধি হয়েছে।
তাই, মেসেজিং, ক্রিয়েটিভ ও ফাইন্যান্স জুড়ে, আমাদের কাছে উপলভ্য এইসব API ব্যবহার করে দেখার জন্য আমরা আপনাকে উৎসাহিত করি।
আমরা মনে করি যে সম্ভাবনা সীমাহীন এবং এই ধরনের অনেক ব্যবহারের ক্ষেত্রে আপনার কল্পনাশক্তি আমাদের চেয়ে ভালো হতে পারে, তাই আমরা একসাথে পার্টনার হতে এবং কীভাবে একসাথে কাজ করতে পারি তা খুঁজে বের করতে চাই।
তাহলে এখন আপনি হাই-লেভেল সুবিধাগুলি বুঝতে পেরেছেন।
তাই আপনি হয়ত এই ধরনের প্রশ্ন জিজ্ঞাসা করতে পারেন, যেমন, ঠিক আছে, আসলে রিচ কেমন?
ডিভাইসে পারফর্ম্যান্স কেমন?
স্পিডের কথা বললে, এটি আসলে কতটা ভালো?
অথবা মডেলের কোয়ালিটি কেমন, তাই তো?
তাই আমরা এগুলি এক এক করে দেখব।
পৌঁছানোর কথা বলতে গেলে, আমরা ইতিমধ্যেই ১৭৫ মিলিয়ন AICore ডিভাইসে উপলভ্য।
আমরা আশা করি যে আগামী বছরে এটি উল্লেখযোগ্যভাবে প্রসারিত হবে এবং আরও কোটি কোটি ডিভাইসে এই সুবিধা পাওয়া যাবে।
এটি সম্ভব হওয়ার অন্যতম কারণ হল, আমরা খুব সহজ একটি হার্নেস তৈরি করেছি।
তাই, আপনাকে শুধু ML Kit API ইন্টিগ্রেট করতে হবে।
ইনপুট ও আউটপুট সংক্রান্ত নিরাপত্তা সিগন্যাল ম্যানেজ করা, ওয়েট সহ মডেল ডাউনলোড করা ম্যানেজ করা, রানটাইমে কীভাবে এটি সেট-আপ করা যায় তা ভাবা, এইসবের জন্য AICore-এ অনেক জটিলতা তৈরি করা হয়েছে।
তাই, ML Kit-কে এমনভাবে তৈরি করা হয়েছে যাতে আপনি শুধু ব্যবহারকারীদের দুর্দান্ত অভিজ্ঞতা দেওয়ার ব্যাপারে মনোযোগ দিতে পারেন এবং বাকি সব কিছু আমরা সামলে নিতে পারি।
ডেভিড চৌ
আমরা আনন্দের সাথে জানাচ্ছি যে Gemini Nano চালানোর জন্য প্রয়োজনীয় RAM-এর পরিমাণ আমরা উল্লেখযোগ্যভাবে কমিয়েছি। Gemini Nano হল প্রাথমিক মডেল যা এইসব GenAI API-এর অনেকগুলিকে চালায়।
তাই, আমরা ৩.৬ জিবি RAM ব্যবহার থেকে কমিয়ে ১.১ করেছি।
তাই, এটি সম্ভব করার জন্য আমরা মূলত ৪টি মূল উন্নতি করেছি।
প্রথমটি হল, ৪-বিটের পরিবর্তে ২-বিট কোয়ান্টাইজেশন ব্যবহার করা।
তাই, অর্ধেক বিট মানে অর্ধেক বাইট যা আপনাকে মডেলের মাধ্যমে পাইপ করতে হবে।
দ্বিতীয়টি হল শেয়ার করা KV ক্যাশে ব্যবহার করা।
তাই, আমরা মডেলের প্রথম ২০টি লেয়ারকে ৩৫ এমবি প্যাকেজে শেয়ার করার অনুমতি দিয়ে এটিকে উল্লেখযোগ্যভাবে কমিয়েছি, যার অর্থ হল আপনি এখনও এই অত্যন্ত ছোট প্যাকেজ সাইজের মধ্যে ১৬ হাজার টোকেন কনটেক্সট সাইজ অ্যাক্সেস করতে পারবেন।
তৃতীয়টি হল, লেজি ভিশন ও অডিও এনকোডার চালু করা।
তাই, আমরা যে টেক্সট ব্যবহারের অনেক কেস দেখছি, তার জন্য আপনাকে এগুলি লোড করতে হবে না।
এবং এর ফলে আপনার আরও হাফ গিগাবাইট RAM সাশ্রয় হবে।
এবং তারপরে, অবশেষে, ব্যাটারির আয়ু উন্নত করার জন্য, আমরা NPU ক্লক স্কেলিংও চালু করেছি।
এর অর্থ হল, NPU যতটা প্রয়োজন ততটা দ্রুত কাজ করতে পারে যাতে ব্যাটারির চার্জ সাশ্রয় হয় এবং আপনার ব্যবহারের ক্ষেত্রে খুব ভাল কোয়ালিটি প্রদান করা নিশ্চিত করে।
স্পিডআপের ক্ষেত্রে আমরা আরও কিছু উন্নতি করেছি।
তাই প্রথমটি হল প্রিফিক্স ক্যাশিং চালু করা।
আপনি দেখতে পাচ্ছেন যে এখানে এই উদাহরণে, সিস্টেম প্রম্পট একাধিকবার ব্যবহার করা হয়েছে, তাই না?
তাই এটি প্রসেস করতে অনেক খরচ হয়।
কিন্তু প্রিফিক্স ক্যাশিংয়ের মাধ্যমে আমরা বলতে চাইছি যে আপনাকে সিস্টেম প্রম্পট বারবার রিপ্রসেস করতে হবে না, তাই তো?
আপনি এটি প্রিফিক্স ক্যাশে সেভ করতে পারেন, যার অর্থ হল এই উদাহরণের জন্য, আপনি যে টোকেনগুলি পাস করতে চান তার ৬০% সেভ করছেন, যার অর্থ হল আপনি যে ডিভাইসেই থাকুন না কেন ৬০% স্পিডআপ।
আমরা দ্বিতীয় যে উন্নতি করেছি তা হল স্পেকুলেটিভ ডিকোডিং।
এই উদাহরণে, আপনি দেখতে পাচ্ছেন যে Gemini Nano প্রতি পাসে একটি করে টোকেন জেনারেট করছে।
এর কারণ হল LLM অটো-রিগ্রেসিভ, অর্থাৎ পরবর্তী টোকেন খুঁজে বের করার জন্য এটি আগে জেনারেট করা টোকেনগুলি দেখতে থাকে।
তাই, আমরা এই সমস্যাটি সমাধান করার জন্য একটি ড্রাফ্টার মডেল তৈরি করেছি যা অনেক দ্রুত গতিতে একগুচ্ছ টোকেন তৈরি করে।
এই ড্রাফটার মডেলটি Gemini Nano-এর থেকে যথেষ্ট ছোট, তাই এটি একবারে একগুচ্ছ টোকেন তৈরি করতে পারে, কোনটি আসলে Gemini Nano-এর অনুমানের সাথে মেলে তা দেখার জন্য Gemini-তে পাঠাতে পারে।
এই উদাহরণে আপনি দেখতে পাচ্ছেন যে প্রথম পাসে এটি ছয়টি টোকেন জেনারেট করে, পরেরটিতে পাঁচটি এবং তৃতীয় পাসে এটি সম্পূর্ণ হয়ে যায়।
আমরা যা দেখছি তা হল, অনেক ইন্টার্নাল ব্যবহারের ক্ষেত্রে, এটি গড়ে ২ গুণ স্পিডআপের দিকে নিয়ে যাচ্ছে।
আপনার হার্ডওয়্যারের উপর নির্ভর করে আপনি আরও দ্রুত উন্নতি দেখতে পেতে পারেন।
ঠিক আছে।
তাই, আপনি জানেন যে, স্পিডের প্রসঙ্গে ফিরে আসলে, আপনি আসলে এই নাটকীয় স্পিডআপ দেখতে পাবেন, ঠিক, অন-ডিভাইস এবং ক্লাউডের মধ্যে, এমনকি 5G নেটওয়ার্কেও।
আপনি দেখতে পাচ্ছেন যে ক্লাউডে সেই ডেটা আপলোড করতে মাত্র এক সেকেন্ডের মতো সময় লাগবে, ১,০০০ থেকে ১,৩০০ ইনপুট টোকেনের জন্য এবং তাই ক্লাউড জার্নিতে প্রিফিল এবং ডিকোড দ্রুততর হতে পারে, তবে ডিভাইসে রান করার সময় এন্ড-টু-এন্ড জার্নি এখনও যথেষ্ট দ্রুত।
এবং তারপরে, কোয়ালিটির কথা বলতে গেলে, আনন্দের সাথে জানাচ্ছি যে এই 2B মডেলটি আসলে গত বছরের 27B মডেলের বিরুদ্ধে পারফর্ম করছে।
যদিও এটি আপনি যেমন জানেন তেমন পারফর্ম্যান্স নয়, আমাদের কাছে আজ যে ফ্রন্টিয়ার মডেলগুলি রয়েছে, আমরা এখানে যা দেখাচ্ছি তা হল এই অন-ডিভাইস মডেলগুলিতে AI-এর ক্ষেত্রে যথেষ্ট অগ্রগতি হয়েছে, তাই না?
তাই আপনি দেখতে পাচ্ছেন যে MMLU-Pro-এর জন্য, যা একটি স্ট্যান্ডার্ড নলেজ বেঞ্চমার্ক, 2B মডেলটি গত বছরের 27B প্যারামিটার মডেলের মতোই ভালো।
এবং তারপরে যখন গ্র্যাজুয়েট সায়েন্স, রিজনিং এবং এজেন্টিভ টুল ব্যবহারের বেঞ্চমার্কের কথা আসে, তখন এই 2B মডেলটি আসলে আরও ভাল, যদিও এটি গত বছরের মডেলের আকারের ১০ ভাগের ১ ভাগের চেয়েও কম।
তাই আমরা আশা করি যে এই ট্রেন্ড বজায় থাকবে।
আমরা GDM টিমের সাথে ঘনিষ্ঠভাবে কাজ চালিয়ে যাচ্ছি যাতে আমরা মডেলে এই উন্নতিগুলি করতে পারি এবং আপনি এগুলি থেকে সেরা ভ্যালু পেতে পারেন।
ডেভিড চৌ
ঠিক আছে।
সুতরাং, আমি আপনাকে অনেক সুবিধা এবং নতুন উন্নতির কথা বলেছি এবং আপনি হয়তো জিজ্ঞাসা করছেন, "আমরা আসলে এই মডেলগুলি দিয়ে কী করতে পারি?"
এবং তাই এই সমস্ত ক্ষমতা উন্নতি আছে যে আমরা API পাশাপাশি তৈরি করা হয়েছে.
তাই সিস্টেম নির্দেশাবলীর মাধ্যমে, আপনি একটি পারসোনা সেট করতে পারেন।
মাল্টি-ইমেজ ইনপুটের মাধ্যমে, আপনি একসাথে অনেক ছবি আপলোড করতে পারবেন।
এছাড়াও, আমরা এই বছরের শুরুতে থিঙ্কিং মোড চালু করেছি, যাতে আপনি রিজনিং সংক্রান্ত সমস্যা এবং টুল কলিংয়ের জন্য আরও বেশি সময় ব্যয় করতে পারেন।
এবং অবশেষে, স্ট্রাকচার্ড আউটপুটের মাধ্যমে, আমরা এটিও করেছি যাতে আপনি একটি Kotlin ক্লাস অ্যানোটেট করতে পারেন এবং আপনার নির্দিষ্ট স্কিমার সাথে মেলে এমন একটি অবজেক্ট ফেরত পেতে পারেন।
এছাড়াও, আমরা ডেভেলপারদের কাছে জানতে চাই যে তারা নতুন কী কী উন্নতি করতে চান।
তাই, সমীক্ষা করার সময় আমরা যেসব ডেভেলপারদের সাথে কথা বলেছি তাদের মধ্যে ৯১% বলেছেন যে, এইসব ক্ষমতার মধ্যে কোনও একটি উপলভ্য থাকলে তারা একটি অভিজ্ঞতা লঞ্চ করবেন।
তাই, এই সপ্তাহের শুরুতে, আমরা এই চারটি ক্ষমতা লঞ্চ করেছি: স্পিচ ট্রান্সলেশন, এম্বেডিং, টেক্সট-টু-স্পিচ এবং ফাংশন কলিং।
স্পিচ ট্রান্সলেশনের মাধ্যমে, আপনি ২০টিরও বেশি ভাষা থেকে অডিও ট্রান্সক্রাইব করতে পারবেন।
এম্বেডিংয়ের মাধ্যমে, আপনি শুধু কীওয়ার্ড সার্চ ব্যবহার করার পরিবর্তে, শব্দার্থগত অর্থ দ্বারা আপনার অ্যাপের কন্টেন্ট সার্চ করতে পারবেন।
টেক্সট-টু-স্পিচের মাধ্যমে, আপনি এখন আপনার অভিজ্ঞতার জন্য মানুষের মতো ভয়েস প্রদান করতে পারেন, তাই আপনাকে আর রোবোটিক ভয়েস ব্যবহার করতে হবে না।
এবং অবশেষে, ফাংশন কলিংয়ের মাধ্যমে, আপনি আসলে আপনার অ্যাপকে আপনার ব্যবহারকারীদের হয়ে অ্যাকশন নিতে দিতে পারেন।
তাই, আমরা ইতিমধ্যেই কয়েকজন ডেভেলপারের সাথে পার্টনারশিপ করেছি যাতে তারা নিজেদের অভিজ্ঞতায় কীভাবে ইন-অ্যাপ এজেন্ট তৈরি করতে পারেন তা দেখতে পারি এবং আমরা আগামী বছরের শেষের দিকে এটি লঞ্চ করার আশা করছি।
ডেভিড চৌ
সুতরাং, এই সমস্ত ক্ষমতা দেওয়া হয়েছে, আসুন আসলে একটি এন্ড-টু-এন্ড উদাহরণের মাধ্যমে হেঁটে যাই যেখানে আমরা এই সমস্ত ক্ষমতার সাথে তৈরি করছি।
তাহলে, ধরা যাক একজন আমেরিকান ফটোগ্রাফার জাপানে ভ্রমণ করছেন।
এবং, আপনি ফটো থেকে দেখতে পাচ্ছেন, তাদের কাছে স্পষ্টতই পর্যাপ্ত ক্যামেরা বা লেন্স নেই।
তাই, তিনি নতুন লেন্স অর্ডার করে হোটেলে ডেলিভারি দিতে বলেন।
কিন্তু একটি সমস্যা আছে: সারাদিনের কাজকর্মের মধ্যে তিনি একটি ভয়েসমেল পান এবং তাকে বলা হয় যে দিনের শেষে প্যাকেজটি পেতে হলে তাকে ৬৮০০ জাপানি ইয়েন ক্লিয়ারেন্স ফি দিতে হবে।
তিনি একটি টেক্সট মেসেজও পেয়েছেন যাতে হোল্ডে থাকা পার্সেলের ফটো আছে।
সুতরাং, এই ক্ষেত্রে, ব্যবহারকারী কোনও ধরনের স্ক্যাম শনাক্তকরণ সুরক্ষা পেতে আগ্রহী, তাই তো?
তারা বুঝতে চান যে কীভাবে তারা ভয়েসমেল বৈধ নাকি জালিয়াতি তা শনাক্ত করতে পারবেন।
তাই, আমরা সব তথ্য ব্যক্তিগত রাখতে চাই, আমরা নিশ্চিত করতে চাই যে তথ্যটি ভিত্তিহীন নয় এবং এটি ব্যাখ্যাযোগ্য।
তাই আমরা নিশ্চিত করতে চাই যে তারা জাপানি টেক্সট বুঝতে পারবেন এবং অ্যাক্সেসিবিলিটি সংক্রান্ত প্রয়োজন থাকলে, উপলভ্য যেকোনও ভাষা ব্যবহার করতে পারবেন।
তাহলে, এখানে চারটি ধাপ থাকবে, তাই তো?
প্রথম ধাপ হল, সেই ভয়েসমেল টেক্সটে প্রসেস করা, তাই এটি সেই ভয়েসমেল অনুবাদ করবে।
আমরা এর জন্য স্পিচ ট্রান্সলেশন API ব্যবহার করব।
এবং তারপর তারা চাইলে প্রশ্ন জিজ্ঞাসা করতে পারবে, তাই আমরা স্পিচ রিকগনিশন API ব্যবহার করতে চলেছি।
তারপর, আমরা এমন ফাংশন নির্ধারণ করতে চাই যা ব্যবহারকারীর প্রয়োজনীয় তথ্য খুঁজে পেতে পারে।
তাই, আমরা Prompt API ও ফাংশন কলিং ব্যবহার করতে চলেছি।
এখন, এটি ব্যাকগ্রাউন্ডে চলবে এবং ব্যবহারকারী কিছু দেখতে পাবেন না।
এবং তারপরে তৃতীয়ত, আমরা আসলে এম্বেডিং API এবং AppSearch ব্যবহার করে কীভাবে সেই তথ্য স্টোর এবং রিট্রিভ করতে হয় তা খুঁজে বের করব।
এটি ব্যাকগ্রাউন্ডেও চলবে।
এবং তারপরে, অবশেষে, আমরা Prompt API, স্ট্রাকচার্ড আউটপুট এবং স্পিচ সিন্থেসিস ব্যবহার করে যে তথ্য সংগ্রহ করছি তার উপর ভিত্তি করে একটি রায় দেব।
ঠিক আছে।
তাহলে, শুরু করা যাক।
আমরা স্পিচ অনুবাদ ও স্পিচ রিকগনিশন ব্যবহার করতে চলেছি।
তাহলে, প্রথম ধাপ হল, সেই ভয়েসমেল ও টেক্সট নিয়ে সেটিকে প্রয়োজনীয় তথ্যে পরিণত করা, তাই তো?
তাই, ব্যবহারকারী এই মেসেজ পেয়েছেন, "আপনাকে এই ৬,৮০০ জাপানি ইয়েন ফি দিতে হবে।
শুল্ক, হয়ত আগে থেকে পেমেন্ট করা হয়নি, তাই আপনাকে এটি পেমেন্ট করতে হবে।"
তাই, তারা সেই অনুবাদ পেতে চান, তাই তো?
তাই, আমরা এখানে যে বিষয়টি উল্লেখ করতে চাই তা হল, অনুবাদ এবং তথ্য সম্পর্কে ব্যবহারকারীর প্রশ্ন, দুটিই একই অডিও এনকোডার ব্যবহার করে Gemini Nano 4-এ যায়, কারণ অডিও এনকোডারকে ১০০টিরও বেশি ভাষায় ট্রেনিং দেওয়া হয়েছে।
এবং যাতে আপনি সেই অনুবাদ পেতে পারেন, তার পাশাপাশি ব্যবহারকারীর প্রশ্নের টেক্সটের জন্য ASR পেতে পারেন।
তাই, অনুবাদের জন্য আমাদের শুধু সোর্স ও গন্তব্য ভাষা সেট-আপ করতে হবে।
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 ব্যবহার করব।
তাই, ব্যবহারকারীর জার্নিতে, যেখানে তিনি লেন্স অর্ডার করেছেন, সেখানে আমরা আসলে সেই তথ্য ইন্ডেক্স করতে চাই যাতে ব্যবহারকারী এবং ডেভেলপার দুজনেই তা অ্যাক্সেস করতে পারেন, তাই তো?
তাই, এতে আমাদের কাছে অর্ডার প্যাকেজ নম্বর সম্পর্কে তথ্য আছে।
এটির একটি ট্র্যাকিং নম্বরও আছে।
এটি কোথায় পাঠানো হয়েছে সেই সম্পর্কে কিছু বিবরণ এতে রয়েছে।
এবং আপনি দেখতে পাচ্ছেন যে এখানে আগে থেকেই উল্লেখ করা আছে যে ডিউটি প্রিপেড ছিল।
তাই, আমরা যা করতে চলেছি তা হল, সেই তথ্যের সাথে আমাদের জন্য গুরুত্বপূর্ণ হতে পারে এমন অন্য যেকোনও তথ্য ইন্ডেক্স করার জন্য একটি মাল্টিমোডাল এম্বেডিং তৈরি করা।
তাই, এটি কেমন দেখতে লাগবে তা এখানে দেওয়া হল।
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 আমাদের সেই তথ্য পেতে সাহায্য করে।
এবং তারপরে, অবশেষে, আমরা তথ্য আনার জন্য ন্যূনতম কোসাইন সাদৃশ্য কী তা নির্ধারণ করতে 0.68-এর একটি থ্রেশহোল্ড নির্দিষ্ট করতে যাচ্ছি।
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 সেট-আপ করা এবং আমাদের সম্ভাব্য বিভিন্ন ধরনের বিরোধের কয়েকটি ভিন্ন enum সেট-আপ করা, তাই না?
সেটি 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,
)তাই আপনি এখানে দেখতে পাচ্ছেন যে, এটি খুবই সহজ।
আমরা শুধু জানতে চাইছি যে এটা কি কোনও স্ক্যাম?
আমাদের কাছে হয়ত একটি 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
}বেশিরভাগ ডিভাইসে এই উত্তর জেনারেট করতে ১২০ মিলিসেকেন্ডের কম সময় লাগবে।
প্রয়োজন অনুযায়ী আমরা মেমরি মুছে দেব।
And then if needed, we can also set a fallback, right, using platform APIs.
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 ব্যবহার করেছি।
যেমন, স্পিচ রিকগনিশন ও ট্রান্সলেশন API, প্রম্পট API ও ফাংশন কলিং, এম্বেডিং ও AppSearch, স্ট্রাকচার্ড আউটপুট ও স্পিচ সিন্থেসিস ব্যবহার করে।
এবং তাই এটি একটি উদাহরণ মাত্র যে কীভাবে আপনি এই সমস্ত ক্ষমতাকে একটি সুসংগত উপায়ে কাজে লাগাতে পারেন এবং এটি সম্পূর্ণরূপে ডিভাইসে চালাতে পারেন।
আর তারপর আমরা শুরুতে যা নিয়ে কথা বলছিলাম, সেখানে ফিরে যাই, এটি চালানোর অনেক সুবিধা আছে, তাই না?
আপনি যদি আজ আপনার ডিভাইসে এটি রান করেন, তাহলে দেখতে পাবেন যে বেশিরভাগ হার্ডওয়্যারে এটি ডিভাইসে অনেক দ্রুত কাজ করে।
এতে আপনার কোনও খরচ হয় না, তাই না? আর আপনি যদি এটি সবসময় চালান, তাহলে আপনি সম্ভাব্যভাবে লক্ষ লক্ষ ডলার সাশ্রয় করছেন।
এবং তারপরে এটি উপলভ্য, তাই না, ব্যবহারকারী পাহাড়ে বা বেসমেন্টে আছেন এবং ইন্টারনেটে অ্যাক্সেস নেই কিনা তা নির্বিশেষে।
এবং এটি সম্পূর্ণ ব্যক্তিগত কারণ আপনাকে ক্লাউডে কোনও তথ্য পাঠাতে হবে না।
তাই, এই সমস্ত ক্ষমতা দেওয়া হয়েছে, অনুগ্রহ করে নির্দ্বিধায় ডকুমেন্টগুলি দেখুন, QR কোডটি দেখুন বা লিঙ্কে যান।
এগুলি হল সেইসব ক্ষমতা যা আমরা গত প্রায় ছয় মাস ধরে লঞ্চ করেছি।
এই আলোচনায় যোগ দেওয়ার জন্য সময় বের করার জন্য আপনাদের সকলকে ধন্যবাদ।
আমাদের সাথে সময় কাটানোর জন্য আপনাকে ধন্যবাদ।
তাই, আপনাকে অনেক ধন্যবাদ।
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 ...