ในเซสชันนี้ เราจะสำรวจ Use Case ต่างๆ ของ GenAI โดยใช้ ML Kit API เพื่อทำความเข้าใจว่า GenAI ในอุปกรณ์เหมาะกับแอปเมื่อใด และทำความเข้าใจวิธีประเมิน Use Case ของ GenAI ที่มีอยู่เพื่อดูว่าโมเดลในอุปกรณ์สามารถตอบสนองแถบคุณภาพได้หรือไม่
ข้อความถอดเสียง
David Chou
มีคนอยู่ไหม
สวัสดีตอนบ่ายทุกคน
ฉันชื่อ David Chou
ฉันเป็นหัวหน้าฝ่ายผลิตภัณฑ์ของ Android AICore
เราตื่นเต้นมากที่จะได้พูดคุยกับคุณเกี่ยวกับความสามารถของ AI ในอุปกรณ์
วันนี้เราจะพูดถึงเรื่องสำคัญ 2-3 เรื่องด้วยกัน
อย่างแรกคือการพิจารณาว่าทำไมคุณจึงควรใช้ AI ในอุปกรณ์ โดยเฉพาะอย่างยิ่งเมื่อพิจารณาถึงความก้าวหน้าของ AI ในระบบคลาวด์ที่รวดเร็วขึ้น
ประการที่ 2 คือการพิจารณาการปรับปรุงบางอย่างที่เราได้ทำในช่วงปีที่ผ่านมาเพื่อทำให้โมเดล AI ในอุปกรณ์เหล่านี้มีประสิทธิภาพสูงมาก
และสิ่งสุดท้ายที่เราจะพูดถึงคือตัวอย่างแบบครบวงจรที่แสดงให้เห็นว่าคุณสามารถใช้ประโยชน์จากข้อดีทั้งหมดเหล่านี้ได้อย่างไรโดยใช้ AI ในอุปกรณ์
ก่อนอื่นเลย ทำไมต้องใช้ AI ในอุปกรณ์
เหตุผลหลักๆ มีอยู่ด้วยกัน 4 ข้อ
อย่างแรกคือความเร็ว อย่างที่ 2 คือค่าใช้จ่าย อย่างที่ 3 คือความพร้อมใช้งาน และอย่างที่ 4 คือความเป็นส่วนตัว
ดังนั้นเมื่อพูดถึงความเร็ว คุณจะเห็นได้ว่าการอนุมาน AI ในอุปกรณ์กำลังมาถึงจุดที่เร็วกว่าการทำงานในระบบคลาวด์ เพียงเพราะคุณไม่ต้องส่งข้อมูลไปมาระหว่างอุปกรณ์ของผู้ใช้กับระบบคลาวด์
ดังนั้น ในกรณีการใช้งานหลายๆ กรณีที่เราได้ทดสอบ เราพบว่า AI ในอุปกรณ์ทำงานได้เร็วขึ้นถึง 2 เท่าเมื่อเทียบกับระบบคลาวด์
ประการที่ 2 คือการคำนึงถึงต้นทุน
ลองจินตนาการว่าคุณต้องเรียกใช้การอนุมาน 1 ล้านครั้งต่อวัน
คุณสามารถประหยัดเงินได้หลายแสนดอลลาร์หากใช้ AI ในอุปกรณ์ โดยเฉพาะจากอุปกรณ์ของผู้ใช้เทียบกับการใช้ในระบบคลาวด์
และประการที่ 3 คือความพร้อมใช้งานแบบออฟไลน์
เราจึงได้ส่งแบบสำรวจไปเมื่อต้นปีนี้ และ 62% ของนักพัฒนาซอฟต์แวร์ที่เราพูดคุยด้วยกล่าวว่าความพร้อมใช้งานแบบออฟไลน์เป็นเหตุผล 3 อันดับแรกในการใช้ AI ในอุปกรณ์
ดังนั้น ไม่ว่าผู้ใช้จะอยู่บนเครื่องบินที่ความสูง 35,000 ฟุตหรืออยู่ในชั้นใต้ดินของสถานีรถไฟโตเกียวและไม่มีอินเทอร์เน็ต ก็ยังใช้ประโยชน์จากความสามารถทั้งหมดนี้ได้
และสุดท้าย ความเป็นส่วนตัวเป็นหนึ่งในเหตุผลที่สำคัญที่สุดที่เราพบเมื่อสำรวจนักพัฒนาแอป
โดย 72% จัดให้เหตุผลนี้อยู่ใน 3 อันดับแรก
40% จัดอันดับให้เป็นอันดับหนึ่ง
ดังนั้นเมื่อพูดถึงข้อความ บันทึกเสียง รูปภาพ หรือแม้แต่ข้อมูลทางการเงินหรือทางการแพทย์ ผู้ใช้ต้องการเก็บข้อมูลนี้ไว้เป็นส่วนตัว ดังนั้นการอนุมานในอุปกรณ์จึงมีประโยชน์อย่างมากสำหรับผู้ใช้
เมื่อพิจารณาถึงประโยชน์ทั้งหมดเหล่านี้ เราจึงสร้าง ML Kit ขึ้นมาเพื่อให้คุณสามารถรักษาและใช้ประโยชน์จากความสามารถทั้งหมดเหล่านี้ได้
เราพยายามทำให้ขั้นตอนนี้ง่ายที่สุด
ดังนั้น หากคุณมีข้อร้องเรียนใดๆ โปรดติดต่อเรา
เราจะให้ข้อมูลติดต่อไว้ที่ตอนท้ายของการพูดคุยนี้
เรายินดีที่จะช่วยหาวิธีเปิดใช้กรณีการใช้งานของคุณ
ดังนั้นในเบื้องต้น เราจึงได้เปิดใช้ความสามารถต่างๆ สำหรับกรณีการใช้งานด้านคำพูดและการสร้างสรรค์
เช่น การพิสูจน์อักษร การเขียนใหม่ และการเปิดใช้การเขียนพรอมต์แบบหลายรูปแบบที่ยืดหยุ่น
ดังนั้น เป้าหมายของเราคือการพยายามทำให้ฟีเจอร์นี้ใช้งานได้มากที่สุด
และนี่คือเหตุผลที่ผู้ใช้และนักพัฒนาแอปจำนวนมากเริ่มนำฟีเจอร์นี้ไปใช้แล้ว
ปัจจุบันเรามีแอปของบุคคลที่สาม 9 แอปที่มียอดดาวน์โหลด 10 ล้านครั้งซึ่งใช้ฟีเจอร์ GenAI เหล่านี้ในเวอร์ชันที่ใช้งานจริง
และเราเห็นการปรับขนาดเพิ่มขึ้นอย่างมากทั้งในแอปของบุคคลที่หนึ่งและแอปของบุคคลที่สาม รวมถึงการเติบโต 110% QoQ ในการเปิดตัวแอปสำหรับฟีเจอร์การผลิต AI
ดังนั้น เราจึงขอแนะนำให้คุณลองใช้ API ที่เรามีให้ ไม่ว่าจะเป็นการรับส่งข้อความ ครีเอทีฟโฆษณา และการเงิน
เราคิดว่าความเป็นไปได้นั้นไม่มีที่สิ้นสุด และคุณน่าจะมีจินตนาการที่ดีกว่าเราในกรณีการใช้งานเหล่านี้หลายๆ กรณี ดังนั้นเราจึงอยากร่วมมือกันและหาแนวทางที่เราจะทำงานร่วมกันได้
ตอนนี้คุณเข้าใจประโยชน์ในระดับสูงแล้ว
คุณอาจมีคำถาม เช่น การเข้าถึงเป็นอย่างไร
ประสิทธิภาพในอุปกรณ์เป็นอย่างไร
เมื่อพูดถึงความเร็ว ความเร็วนี้ดีแค่ไหนกันแน่
หรือคุณภาพของโมเดลเองใช่ไหม
เราจะมาดูแต่ละขั้นตอนกัน
ในแง่ของการเข้าถึง ปัจจุบันเราพร้อมให้บริการบนอุปกรณ์ AICore 175 ล้านเครื่องแล้ว
เราคาดว่าเครือข่ายนี้จะขยายตัวอย่างมากในปีหน้า โดยจะเพิ่มอุปกรณ์อื่นๆ อีกหลายร้อยล้านเครื่อง
ส่วนหนึ่งที่ทำให้สิ่งนี้เป็นไปได้คือเราได้สร้างชุดสายไฟที่เรียบง่ายมาก
ดังนั้น ML Kit API จึงเป็นสิ่งเดียวที่คุณต้องผสานรวม
AICore มีความซับซ้อนอย่างมากในการจัดการสัญญาณความปลอดภัยของอินพุตและเอาต์พุต จัดการการดาวน์โหลดโมเดลพร้อมกับน้ำหนัก และพิจารณาวิธีตั้งค่าในรันไทม์
ดังนั้น เป้าหมายของเราคือการทำให้ ML Kit ใช้งานง่าย เพื่อให้คุณไม่ต้องกังวลเกี่ยวกับวิธีมอบประสบการณ์ที่ยอดเยี่ยม และเราจะจัดการส่วนที่เหลือให้เอง
David Chou
นอกจากนี้ เรายังยินดีที่จะประกาศว่าเราได้ลด RAM ที่จำเป็นในการเรียกใช้ Gemini Nano ซึ่งเป็นโมเดลหลักที่ขับเคลื่อน GenAI API จำนวนมากเหล่านี้ลงอย่างมาก
เราจึงลดการใช้ RAM จาก 3.6 GB เหลือ 1.1 GB
เราได้ทำการปรับปรุงหลักๆ 4 อย่างเพื่อให้สิ่งนี้เป็นไปได้
วิธีแรกคือการใช้การแปลงค่าแบบ 2 บิตแทน 4 บิต
ดังนั้น บิตที่ลดลงครึ่งหนึ่งจึงหมายถึงไบต์ที่ลดลงครึ่งหนึ่งซึ่งคุณต้องส่งผ่านโมเดล
วิธีที่ 2 คือการใช้ KV Cache ที่ใช้ร่วมกัน
เราจึงลดขนาดลงอย่างมากโดยอนุญาตให้โมเดล 20 เลเยอร์แรกแชร์ข้อมูลนั้นในแพ็กเกจขนาด 35 MB ซึ่งหมายความว่าคุณจะยังคงเข้าถึงขนาดบริบทโทเค็น 16,000 รายการได้ภายในแพ็กเกจขนาดเล็กมากนี้
วิธีที่ 3 คือการเปิดใช้โปรแกรมเปลี่ยนไฟล์วิดีโอและเสียงแบบ Lazy
ดังนั้น สำหรับกรณีการใช้งานข้อความจำนวนมากที่เราเห็น คุณไม่จำเป็นต้องโหลดสิ่งเหล่านี้ตั้งแต่แรก
ซึ่งจะช่วยประหยัด RAM ได้อีกครึ่งกิกะไบต์
และสุดท้าย เพื่อปรับปรุงระยะเวลาการใช้งานแบตเตอรี่ เรายังได้เปิดใช้การปรับขนาดสัญญาณนาฬิกา NPU ด้วย
ทั้งหมดนี้หมายความว่า NPU สามารถทำงานได้เร็วเท่าที่จำเป็นเพื่อประหยัดพลังงาน ขณะเดียวกันก็ยังคงให้คุณภาพสูงใน Use Case ของคุณ
เราได้ทำการปรับปรุงอื่นๆ อีก 2-3 อย่างเกี่ยวกับการเร่งความเร็ว
วิธีแรกคือการเปิดใช้การแคชคำนำหน้า
คุณจะเห็นว่าในตัวอย่างนี้ มีการใช้พรอมต์ของระบบซ้ำหลายครั้งใช่ไหม
ดังนั้นการประมวลผลจึงมีค่าใช้จ่ายสูงมาก
แต่ด้วยการแคชคำนำหน้า เรากำลังบอกว่าคุณไม่จำเป็นต้องประมวลผลพรอมต์ของระบบนั้นซ้ำแล้วซ้ำอีกใช่ไหม
คุณสามารถบันทึกข้อมูลนั้นไว้ในแคชคำนำหน้า ซึ่งหมายความว่าในตัวอย่างนี้ คุณจะประหยัดโทเค็นที่ต้องส่งไปได้ 60% ซึ่งหมายความว่าความเร็วจะเพิ่มขึ้น 60% ไม่ว่าคุณจะใช้อุปกรณ์ใดก็ตาม
การปรับปรุงอย่างที่ 2 ที่เราทำคือการถอดรหัสแบบคาดเดา
และในตัวอย่างนี้ คุณจะเห็นว่า Gemini Nano สร้างโทเค็น 1 รายการต่อการส่งผ่านข้อมูล 1 ครั้ง
เนื่องจาก LLM เป็นแบบถดถอยอัตโนมัติ ซึ่งหมายความว่า LLM ต้องคอยดูโทเค็นก่อนหน้าที่สร้างขึ้นเพื่อหาโทเค็นถัดไป
ดังนั้น วิธีที่เราจัดการกับปัญหานี้คือการสร้างโมเดลร่างที่สร้างโทเค็นจำนวนมากด้วยความเร็วที่สูงขึ้นมาก
โมเดลร่างนี้มีขนาดเล็กกว่า Gemini Nano มาก จึงสามารถสร้างโทเค็นจำนวนมากพร้อมกัน แล้วส่งต่อให้ Gemini เพื่อดูว่าโทเค็นใดตรงกับการคาดเดาของ Gemini Nano
และในตัวอย่างนี้ คุณจะเห็นว่าในการส่งครั้งแรก ระบบจะสร้างโทเค็น 6 รายการ และครั้งถัดไปจะสร้าง 5 รายการ จากนั้นเมื่อถึงเทิร์นที่ 3 ก็จะเสร็จสิ้น
และสิ่งที่เราเห็นคือสำหรับกรณีการใช้งานภายในจำนวนมากที่เรากำลังพิจารณาอยู่ สิ่งนี้ทำให้ความเร็วเพิ่มขึ้นโดยเฉลี่ย 2 เท่า
คุณอาจได้รับการปรับปรุงที่รวดเร็วขึ้น ทั้งนี้ขึ้นอยู่กับฮาร์ดแวร์ของคุณ
ตกลง
นี่จึงเป็นเหตุผลว่าทำไมเมื่อกลับไปดูเรื่องความเร็ว คุณจะเห็นว่าความเร็วเพิ่มขึ้นอย่างมากระหว่างการประมวลผลในอุปกรณ์และระบบคลาวด์ แม้จะใช้เครือข่าย 5G ก็ตาม
คุณจะเห็นว่าการอัปโหลดข้อมูลนั้นไปยังระบบคลาวด์จะใช้เวลาเพียง 1 วินาทีเท่านั้นสำหรับโทเค็นอินพุต 1,000-1,300 รายการ ดังนั้นแม้ว่าการเติมข้อมูลล่วงหน้าและการถอดรหัสใน Cloud Journey อาจเร็วกว่า แต่เส้นทางแบบครบวงจรก็ยังคงเร็วกว่าอย่างมากเมื่อคุณเรียกใช้ในอุปกรณ์
และในเรื่องของคุณภาพ เรายินดีที่จะประกาศว่าโมเดล 2B นี้มีประสิทธิภาพเทียบเท่ากับโมเดล 27B ของปีที่แล้ว
แม้ว่าประสิทธิภาพจะไม่ดีเท่าโมเดล Frontier ที่เรามีในปัจจุบัน แต่สิ่งที่เราแสดงให้เห็นที่นี่คือความก้าวหน้าอย่างมากในเรื่อง AI ในโมเดลในอุปกรณ์เหล่านี้ จริงไหม
คุณจะเห็นว่าสำหรับ MMLU-Pro ซึ่งเป็นเกณฑ์มาตรฐานความรู้มาตรฐาน โมเดล 2B มีประสิทธิภาพเกือบเทียบเท่ากับโมเดลพารามิเตอร์ 27B จากปีที่แล้ว
และเมื่อพูดถึงการเปรียบเทียบด้านวิทยาศาสตร์ระดับบัณฑิตศึกษา การให้เหตุผล และการใช้เครื่องมือ Agentic โมเดล 2B นี้ดีกว่า แม้ว่าจะมีขนาดไม่ถึง 1 ใน 10 ของโมเดลปีที่แล้วก็ตาม
เราจึงคาดว่าแนวโน้มเหล่านี้จะยังคงดำเนินต่อไป
เราจะทำงานร่วมกับทีม GDM อย่างใกล้ชิดต่อไปเพื่อให้แน่ใจว่าเราจะปรับปรุงโมเดลเหล่านี้ต่อไปได้ เพื่อให้คุณได้รับประโยชน์สูงสุดจากโมเดลเหล่านี้
David Chou
ตกลง
เราได้พูดถึงประโยชน์และการปรับปรุงใหม่ๆ มากมาย และคุณอาจสงสัยว่า "เราจะทำอะไรกับโมเดลเหล่านี้ได้บ้าง"
ดังนั้นเราจึงได้ปรับปรุงความสามารถทั้งหมดนี้ใน API ด้วย
ดังนั้น คุณจึงสามารถกำหนดตัวตนได้ด้วยคำสั่งของระบบ
การป้อนข้อมูลแบบหลายรูปภาพช่วยให้คุณอัปโหลดรูปภาพหลายรูปพร้อมกันได้
นอกจากนี้ เรายังได้เปิดใช้โหมดการคิดเมื่อต้นปีนี้ เพื่อให้คุณใช้เวลากับปัญหาการให้เหตุผลและการเรียกใช้เครื่องมือได้มากขึ้น
และสุดท้าย ด้วยเอาต์พุตที่มีโครงสร้าง เรายังทำให้คุณสามารถใส่คำอธิบายประกอบคลาส Kotlin และรับออบเจ็กต์ที่ตรงกับสคีมาที่เฉพาะเจาะจงของคุณได้ด้วย
นอกจากนี้ เรายังพยายามสอบถามนักพัฒนาแอปเกี่ยวกับการปรับปรุงใหม่ๆ ที่ต้องการทำด้วย
และ 91% ของนักพัฒนาแอปที่เราพูดคุยด้วยเมื่อทำการสำรวจกล่าวว่าพวกเขาจะเปิดตัวประสบการณ์หากมีความสามารถเหล่านี้อย่างใดอย่างหนึ่ง
และเมื่อต้นสัปดาห์นี้ เราได้เปิดตัวความสามารถ 4 อย่าง ได้แก่ การแปลเสียงพูด, การฝัง, การอ่านออกเสียงข้อความ และการเรียกใช้ฟังก์ชัน
การแปลเสียงพูดช่วยให้คุณถอดเสียงจากภาษาต่างๆ ได้มากกว่า 20 ภาษา
การฝังช่วยให้คุณค้นหาเนื้อหาของแอปตามความหมายเชิงความหมายได้ แทนที่จะใช้เพียงการค้นหาคีย์เวิร์ด
ตอนนี้คุณสามารถใช้ฟีเจอร์อ่านออกเสียงข้อความเพื่อสร้างเสียงที่เหมือนมนุษย์สำหรับประสบการณ์การใช้งานของคุณได้แล้ว คุณจึงไม่ต้องใช้เสียงหุ่นยนต์อีกต่อไป
และสุดท้าย การเรียกใช้ฟังก์ชันจะช่วยให้คุณอนุญาตให้แอปดำเนินการในนามของผู้ใช้ได้
เราจึงได้ร่วมมือกับนักพัฒนาแอปบางรายเพื่อดูว่าพวกเขาจะสร้าง Agent ในแอปในประสบการณ์การใช้งานของตนได้อย่างไร และเราหวังว่าจะเปิดตัวได้ในช่วงปลายปีหน้า
David Chou
เมื่อพิจารณาถึงความสามารถทั้งหมดนี้แล้ว เรามาดูตัวอย่างตั้งแต่ต้นจนจบที่เราสร้างขึ้นโดยใช้ความสามารถทั้งหมดนี้กัน
ลองจินตนาการว่ามีช่างภาพชาวอเมริกันคนหนึ่งกำลังเดินทางท่องเที่ยวในญี่ปุ่น
และอย่างที่คุณเห็นจากรูปภาพ พวกเขามีกล้องหรือเลนส์ไม่เพียงพออย่างเห็นได้ชัด
จึงสั่งซื้อเลนส์ใหม่และส่งไปที่โรงแรม
แต่มีเงื่อนไขว่า ขณะที่กำลังทำกิจกรรมต่างๆ ในแต่ละวัน พวกเขาได้รับข้อความเสียงที่แจ้งว่าต้องจ่ายค่าธรรมเนียมการผ่านพิธีการศุลกากร 6,800 เยน เพื่อรับพัสดุภายในสิ้นวัน
นอกจากนี้ ผู้ใช้ยังได้รับ SMS ที่มีรูปภาพของพัสดุที่ถูกระงับด้วย
ดังนั้น ในกรณีนี้ ผู้ใช้จึงสนใจที่จะได้รับการปกป้องด้วยการตรวจจับการหลอกลวงบางประเภท ใช่ไหม
ลูกค้าต้องการทราบวิธีระบุว่าข้อความเสียงเป็นข้อความที่ถูกต้องหรือเป็นการฉ้อโกง
ดังนั้นเราจึงต้องการเก็บข้อมูลทั้งหมดไว้เป็นส่วนตัว เราต้องการตรวจสอบว่าข้อมูลนั้นมีที่มาที่ไป และยังอธิบายได้ด้วย
ดังนั้นเราจะตรวจสอบว่าผู้ใช้เข้าใจข้อความภาษาญี่ปุ่น และสามารถใช้ความสามารถด้านภาษาที่มีอยู่ได้ในกรณีที่มีความต้องการด้านการช่วยเหลือพิเศษ
ดังนั้นจะมี 4 ขั้นตอนที่นี่ ใช่ไหม
ขั้นตอนแรกคือการประมวลผลข้อความเสียงนั้นเป็นข้อความ ดังนั้นระบบจะแปลข้อความเสียงนั้น
เราจะใช้ Speech Translation API สำหรับการดำเนินการดังกล่าว
จากนั้นผู้ใช้จะถามคำถามได้หากต้องการ ดังนั้นเราจะใช้ Speech Recognition API
จากนั้นเราต้องการกำหนดฟังก์ชันที่สามารถดึงข้อมูลที่จำเป็นสำหรับผู้ใช้เพื่อให้ได้สิ่งที่ต้องการ
เราจะใช้ Prompt API และการเรียกใช้ฟังก์ชัน
ตอนนี้การดำเนินการนี้จะทำงานในเบื้องหลังโดยที่ผู้ใช้ไม่เห็นอะไรเลย
และประการที่ 3 เราจะหาวิธีจัดเก็บและดึงข้อมูลนั้นโดยใช้ Embedding API และ AppSearch
ซึ่งจะทำงานในเบื้องหลังด้วย
และสุดท้าย เราจะตัดสินโดยอิงตามข้อมูลที่รวบรวม โดยใช้ Prompt API, เอาต์พุตที่มีโครงสร้าง และการสังเคราะห์เสียงพูด
ตกลง
เรามาเริ่มกันเลย
เราจะใช้การแปลเสียงพูดและการจดจำเสียงพูด
ดังนั้น ขั้นตอนแรกคือการนำข้อความเสียงและข้อความนั้นมาเปลี่ยนเป็นข้อมูลที่เป็นประโยชน์ใช่ไหม
ผู้ใช้จึงได้รับข้อความว่า "คุณต้องชำระค่าธรรมเนียม 6,800 เยนนี้
อาจยังไม่ได้ชำระภาษีล่วงหน้า คุณจึงต้องชำระภาษี"
ดังนั้นจึงต้องการให้มีการแปลใช่ไหม
และสิ่งหนึ่งที่เราอยากชี้ให้เห็นตรงนี้คือทั้งคำแปลและคำถามของผู้ใช้เกี่ยวกับข้อมูลทั้งหมดจะส่งไปยัง Gemini Nano 4 โดยใช้ตัวเข้ารหัสเสียงเดียวกัน เนื่องจากตัวเข้ารหัสเสียงได้รับการฝึกด้วยภาษามากกว่า 100 ภาษา
คุณจึงสามารถดูคำแปลนั้น รวมถึง ASR สำหรับข้อความคำถามของผู้ใช้ได้
ดังนั้นสำหรับการแปล สิ่งที่เราต้องทำก็คือตั้งค่าภาษาต้นฉบับและภาษาปลายทาง
val translator = SpeechTranslation.getClient(
SpeechTranslatorOptions.Builder(
Locale.JAPANESE, Locale.US
).build()
)ในกรณีนี้ เราจะใช้ภาษาญี่ปุ่นและภาษาอังกฤษแบบอเมริกัน
เราจะตั้งค่า Kotlin Flow ที่ส่งไบต์ไปยังโปรแกรมแปลเสียงพูดโดยตรง
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
}
}จากนั้นเราจะรวบรวมคำตอบโดยใช้ข้อความที่แปลแล้ว
ตกลง
เยี่ยมมาก
ตอนนี้เราเข้าใจแล้วว่าข้อความเสียงพูดว่าอะไร ถูกไหม
มีพัสดุเลนส์กล้อง
พัสดุติดอยู่ที่ศุลกากร
โดยผู้ใช้จะต้องชำระค่าธรรมเนียม 6,800 เยนดังกล่าว
และตอนนี้ผู้ใช้ต้องการถามคำถามเกี่ยวกับเรื่องนี้
ตกลง
แล้วการกักสินค้าที่ศุลกากรนี้เป็นเรื่องจริงไหม
ในกรณีนี้ เราจะใช้ 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)
}
}ตกลง
ตอนนี้เราได้เสร็จสิ้นส่วนแรกของการรับข้อมูลแล้ว
อย่างที่เห็นว่าโค้ดมีไม่กี่บรรทัด
David Chou
ส่วนที่ 2 คือการคัดแยก ซึ่งเป็นการคิดหาวิธีใช้ประโยชน์จาก 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()เราขอแนะนำอย่างยิ่งให้คุณใช้เมื่อเรียกใช้เครื่องมือ
เหตุผลก็คือเราได้ทำการทดสอบหลายครั้งซึ่งแสดงให้เห็นว่าความแม่นยำเพิ่มขึ้น 13 คะแนนจาก 100 คะแนนเมื่อเรียกใช้เครื่องมือใน Gemini Nano 4
และในบางกรณีก็สูงกว่ามากด้วย ซึ่งอาจให้ผลลัพธ์ที่ดีกว่ามาก
และหากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการเรียกใช้ฟังก์ชัน โปรดเข้าร่วมเซสชันของ Jolanda และ Victoria เพื่อนร่วมงานของฉัน ซึ่งจะจัดขึ้นในภายหลัง
ซึ่งเป็นความสามารถด้าน Agentic AI ในแอปของคุณ
ฉันคิดว่า Victoria อาจจะนั่งอยู่ที่ไหนสักแห่งในกลุ่มผู้ชม ดังนั้นคุณทักทายเธอได้เลย
David Chou
โอเค เจ๋งดี
ตอนนี้เราทำ 2 ขั้นตอนแรกเสร็จแล้ว
มาพูดถึงวิธีการทำงานของการดึงข้อมูลกัน
ดังนั้นเราจะใช้การฝังและ AppSearch
ดังนั้น เราอาจต้องย้อนกลับไป 2-3 ขั้นตอนในเส้นทางของผู้ใช้ ณ จุดที่ผู้ใช้สั่งซื้อเลนส์ สิ่งที่เราต้องการทำคือจัดทำดัชนีข้อมูลนั้นเพื่อให้ผู้ใช้และนักพัฒนาแอปเข้าถึงได้
ดังนั้น ในส่วนนี้เราจึงมีรายละเอียดข้อมูลเกี่ยวกับหมายเลขแพ็กเกจคำสั่งซื้อ
และมีหมายเลขติดตามพัสดุด้วย
ซึ่งมีรายละเอียดเกี่ยวกับสถานที่ที่ส่ง
และคุณจะเห็นว่าที่นี่มีการระบุไว้แล้วว่ามีการชำระภาษีล่วงหน้า
ดังนั้น สิ่งที่เราจะทำคือสร้างการฝังแบบหลายโมดัลเพื่อจัดทำดัชนีข้อมูลนั้นพร้อมกับข้อมูลอื่นๆ ที่อาจสำคัญสำหรับเรา
ซึ่งจะมีลักษณะดังนี้
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เราจะรับอีเมลคำสั่งซื้อ
ขอย้ำอีกครั้งว่าวิธีนี้ง่ายมาก เราเพียงแค่ส่งรูปภาพ 1 รูปและข้อความ 1 รายการ แต่คุณอาจมีเนื้อหาเพิ่มเติมที่ต้องการจัดทำดัชนีที่นี่
จากนั้นเราจะสร้างเวกเตอร์การฝัง
หลังจากนั้น เราจะใช้ 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 ได้มากขึ้น และยังช่วยให้ผู้ใช้มีความเป็นส่วนตัวและความปลอดภัยมากขึ้นด้วย
David Chou
เอาล่ะ และตอนนี้ก็มาถึงขั้นตอนสุดท้ายแล้ว
เรามีข้อมูลทั้งหมดที่ต้องการแล้ว
เราจะใช้ 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 ที่แตกต่างกัน 2-3 รายการสำหรับความขัดแย้งประเภทต่างๆ ที่อาจเกิดขึ้นได้ใช่ไหม
ไม่ว่าจะเป็น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 ที่คุณสามารถตั้งค่าทั้งค่าต่ำสุดและสูงสุดตั้งแต่ 0 ถึง 100
คุณยังแจ้งให้ผู้ใช้ทราบได้ด้วยว่าคุณช่วยประหยัดเงินให้ผู้ใช้ได้เท่าใด หากต้องการติดตามข้อมูลดังกล่าวเมื่อเวลาผ่านไป
จากนั้นคุณยังสามารถระบุความขัดแย้งทางภาพที่เราเพิ่งกำหนดไว้ได้ด้วย
สิ่งหนึ่งที่ควรทราบคือคำอธิบายประกอบ @Guide เป็นแบบไม่บังคับ
คุณไม่จำเป็นต้องตั้งค่าและระบุคำอธิบายประเภทนั้น
ซึ่งจะมีประโยชน์เนื่องจากคุณอาจอธิบายสิ่งที่เกิดขึ้นไม่ได้โดยใช้เพียงคำจำกัดความของตัวแปร
ดังนั้นการมีคำอธิบายนั้นจึงเป็นประโยชน์สำหรับ Gemini Nano ในการพิจารณาว่าจะเรียกใช้ฟังก์ชันใดและจะตั้งค่าอย่างไรเพื่อให้ป้อนข้อมูลที่ถูกต้องในช่องที่ถูกต้อง
หลังจากตั้งค่าแล้ว วิธีการนี้ก็ง่ายมาก
คุณเพียงแค่เรียกใช้โค้ด 3 บรรทัดที่นี่
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
}โดยจะใช้เวลาไม่ถึง 120 มิลลิวินาทีในอุปกรณ์ส่วนใหญ่เพื่อสร้างคำตอบนั้น
เราจะล้างความทรงจำที่จำเป็น
และหากจำเป็น เรายังตั้งค่าการสำรองได้ด้วยโดยใช้ API ของแพลตฟอร์ม
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 ...ซึ่งจะยังคงมีเสียงหุ่นยนต์อยู่ แต่อย่างน้อยก็ช่วยให้ผู้ใช้มีสิ่งที่จะโต้ตอบด้วยในกรณีที่อ่านไม่ได้
David Chou
ตกลง
นั่นเป็นข้อมูลที่เยอะมาก แต่เพื่อสรุปสิ่งที่เราทำ เราได้ใช้ประโยชน์จาก API ต่างๆ เหล่านี้ในกระบวนการนำเข้า การคัดแยก การดึงข้อมูล และการตรวจสอบ
เช่น การใช้ API การรู้จำเสียงและการแปลภาษา, Prompt API และการเรียกใช้ฟังก์ชัน, การฝัง และ AppSearch รวมถึงเอาต์พุตที่มีโครงสร้างและการสังเคราะห์เสียงพูด
และนี่เป็นเพียงตัวอย่างหนึ่งของวิธีที่คุณสามารถใช้ประโยชน์จากความสามารถทั้งหมดเหล่านี้ในลักษณะที่สอดคล้องกันและเรียกใช้บนอุปกรณ์ได้อย่างสมบูรณ์
และเมื่อย้อนกลับไปถึงสิ่งที่เราพูดถึงในตอนต้น การใช้แคมเปญนี้มีประโยชน์อย่างมากใช่ไหม
หากคุณลองเรียกใช้โมเดลนี้ในอุปกรณ์ของคุณในวันนี้ คุณจะเห็นว่าโมเดลนี้ทำงานในอุปกรณ์ได้เร็วกว่าอย่างมากในฮาร์ดแวร์ส่วนใหญ่
คุณไม่ต้องเสียค่าใช้จ่ายใดๆ และอาจประหยัดเงินได้หลายแสนดอลลาร์หากใช้ฟีเจอร์นี้ตลอดเวลา
และฟีเจอร์นี้จะพร้อมใช้งาน ไม่ว่าผู้ใช้จะอยู่บนภูเขาหรือในห้องใต้ดินและไม่มีอินเทอร์เน็ตก็ตาม
และมีความเป็นส่วนตัวอย่างสมบูรณ์เนื่องจากคุณไม่ต้องส่งข้อมูลใดๆ ไปยังระบบคลาวด์
เมื่อพิจารณาถึงความสามารถทั้งหมดนี้แล้ว โปรดดูเอกสาร ดูคิวอาร์โค้ด หรือไปที่ลิงก์
ความสามารถทั้งหมดนี้เป็นความสามารถที่เราเปิดตัวในช่วง 6 เดือนที่ผ่านมาโดยประมาณ
ขอขอบคุณทุกท่านที่สละเวลาเข้าร่วมการพูดคุยในครั้งนี้
ขอขอบคุณอย่างยิ่งที่สละเวลามาพูดคุยกับเรา
ขอบคุณมากๆ เลย
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 ...