مهندس نرمافزار
با ساختن عاملهای خودکار دروندستگاهی، ویژگیهای هوش مصنوعی Android خود را به سطح بالاتری ببرید. در این جلسه، به میاناهای برنامهسازی کاربردی عاملی جدید ML Kit میپردازیم که مستقیماً روی میاناهای برنامهسازی کاربردی دسترسی مستقیم به مدل که قبلاً با آنها آشنا شدهاید ساخته شدهاند.
بااستفاده از برنامه متنباز Jetpacker، یاد خواهید گرفت که چگونه کنشهای چندمرحلهای را در دادههای محلی زنجیرهای کنید، جلسههای چندنوبتی باحالت را با بافتهای مجزا مدیریت کنید، و Android MCP را برای تقویت دستیارهای درونبرنامه و سطوح سطح سیستم Android با یک پایگاه کد واحد متصل کنید.
AgentRunner اجرای ابزار را هماهنگ میکند و روشهای جلسه را برای مکالمههای چندنوبتی بدون ازدست دادن بافت ارائه میدهد.آوانویسی
نام من یولاندا ورهوف است.
من مهندس روابط توسعهدهندگان در Android هستم و امروز ویکتوریا لستاری، که عضو تیم مهندسی AICore است، به من ملحق شده است.
سلام به همه.
از اینکه امروز صبح به ما پیوستید سپاسگزاریم.
بنابراین، تیم ما سالهاست که همکاری نزدیکی داشته است تا مدلهای هوش مصنوعی پیشرفته را از فضای ابری خارج کند و آنها را مستقیماً در دستگاههای Android اجرا کند.
و این تلاشها تحت مجموعه ML Kit GenAI گروهبندی شدهاند.
و از یک سو در این مجموعه ML Kit GenAI، ما میاناهای برنامهسازی کاربردی سطح بالا برای راهحلهای آماده داریم، چیزهایی مثل خلاصه کردن نوشتار، توصیف تصویر، و تشخیص گفتار.
ازسوی دیگر، دسترسی مستقیم به مدل دروندستگاهی Gemini Nano را نیز فراهم کردهایم.
و امروز، درواقع یک گام فراتر میرویم.
بنابراین، با این دسترسی مستقیم، درواقع به شما اجازه میدهیم عاملهای درونبرنامهای بسازید، عاملهایی که میتوانند در چندین نوبت استدلال کنند، بافت مکالمه را ذخیره کنند، و در برنامه شما اقداماتی انجام دهند.
ویکتوریا، منظور واقعی ما از این چیست؟
آره.
تاکنون، وقتی درباره موارد استفاده دروندستگاهی صحبت میکنیم، منظورمان کارهای کوتاهتر، سادهتر، و مجزا است، مثل تشخیص کلاهبرداری احتمالی یا خلاصه کردن تماس تلفنی.
و وقتی میخواهیم چیزی پیچیدهتر بسازیم، مثلاً پرسیدن سؤال درباره کاتالوگ عکسهایتان یا پرسیدن از Google Maps، بهطور طبیعی به مدلهای ابری بزرگ مراجعه میکنیم.
بنابراین، مدلهای ابری مزیت پردازش عظیم و دادههای همزمان را ارائه میدهند، اما هوش مصنوعی دروندستگاهی امکانات جدیدی برای تأخیر کم و پردازش دادههای همزمان و محلی فراهم میکند.
بنابراین، Gemini Nano ازقبل کارهایی را که میتوانید در دستگاههایتان انجام دهید گسترش داده است.
یک نمونه از این ویژگیها «نماگرفتهای Pixel» است که به شما امکان میدهد نماگرفتهای محلی را جستجو کنید و درباره آنها سؤال بپرسید.
بنابراین، وقتی عاملها را در دستگاه اجرا میکنیم، به این معنی است که به جهش بزرگی در قابلیت مدل نیاز داریم و به همین دلیل است که Gemini Nano 4 را میسازیم که میتواند در مراحل مختلف استدلال کند و اقدامات واقعی انجام دهد.
اما این قابلیت جدید فقط زمانی اهمیت دارد که توسعهدهنده دستگاههای واقعی برای هدفیابی داشته باشد.
خبر خوب این است که Gemini Nano 4 درحالحاضر در برخیاز دستگاههای پرچمدار دردسترس است و میتوانید آن را در دستگاههای بسیار بیشتری بااستفاده از «پیشنمای توسعهدهندگان نرمافزار AICore» آزمایش کنید.
و همچنین در مسیر رسیدن به ۱۰۰ میلیون دستگاه در اوایل سال آینده هستیم.
پس این به چه معنا است؟
بنابراین، این بدان معناست که میتوانید از امروز شروع به ساختن و آزمایش این تجربیات کنید، با این آگاهی که آنچه امروز میسازید به مخاطبان گستردهای خواهد رسید.
خب، درباره Gemini Nano 4 صحبت کردیم، درباره دسترسی بالقوه صحبت کردیم، حالا بیایید درباره اینکه قابلیتهای عاملی واقعاً چه معنایی دارند صحبت کنیم.
بنابراین، هوش مصنوعی عاملگرا میتواند برای تکالیف پیچیده برنامهریزی کند، این تکالیف را به چند مرحله تقسیم کند، و سپس این مراحل را در سراسر برنامه شما اجرا کند.
بنابراین، فرض کنید مدل Gemini Nano ما در دستگاه میتواند پایگاه داده محلی شما را پرسمان کند، دادهها را از پسبخش بازیابی کند، استدلال کند و به سؤالات پیچیده شما پاسخ دهد، و همچنین گردش کار برنامه به زبان اصلی دستگاه را در دستگاه شما راهاندازی کند.
اما هوش مصنوعی چگونه از برنامهریزی به انجام اقدامات واقعی میرسد؟
And this is the topic for today, it's Function Calling.
این موتور اصلی قابلیت عاملگرا است.
این مدل زبان طبیعی را به اجرای سیستم ترجمه میکند و با انتخاب و هدایت میاناهای برنامهسازی کاربردی محلی موردنیاز برای حل تکالیف پیچیده، مدل را از استدلالهای منفعل به اجرای فعال سوق میدهد.
همچنین خوشحالیم که اعلام کنیم «فراخوانی تابع» بهطور بومی در Gemini Nano 4 پشتیبانی میشود.
این مدل درحالحاضر در «پیشنمای توسعهدهندگان نرمافزار AICore» دردسترس است و بهزودی برای همه دستگاههای پشتیبانیکننده از Nano 4 عرضه خواهد شد.
بنابراین، عالی است که این نوع قابلیتها را در مدل داریم، اما قابلیت موجود در مدل هنوز میاناهای برنامهسازی کاربردی را به شما بهعنوان توسعهدهنده ارائه نمیدهد.
بنابراین، ما همچنین درحال کار روی افزودن APIهای مناسب به مجموعه ML Kit GenAI بودهایم تا مطمئن شویم که ساختن یک عامل واقعاً آسان میشود.
بنابراین، ما با میاناهای برنامهسازی کاربردی دسترسی مستقیم که ازقبل داشتیم شروع کردیم و انواع تابع و انواع سابقه مکالمه را به آن اضافه کردیم.
و با این دو افزوده جدید به Prompt API در ML Kit، ازاینپس میتوانید این قابلیتهای عاملی سطح پایین را در برنامهتان بسازید.
بااینحال، در آن مرحله کارهای زیادی وجود دارد که باید خودتان انجام دهید.
بنابراین، باید توابع را بهصورت دستی اجرا کنید، سابقه تماس را ذخیره کنید، و همچنین باید خودتان را در چندین تماس تکرار کنید.
بنابراین، اگر چندین فراخوانی وجود دارد که باید انجام دهید، باید، مثلاً، نوعی سیستم حلقهای را خودتان بسازید و باید گاردریلهای خودتان را برای سیستم بسازید.
اکنون این کار امکانپذیر است، اما البته میخواهیم این کار را سریع و آسان کنیم.
بنابراین، علاوهبر این کتابخانه میانای برنامهسازی کاربردی پیامواره، کتابخانه جریانهای عاملی جدیدی نیز اضافه کردهایم.
میتوانید آن را با com.mlkit.genai:agent-api پیدا کنید و درحالحاضر در نسخه آلفا دردسترس است.
اکنون با این میانای برنامهسازی کاربردی جدید، ما بخش زیادی از کارهای سنگین را برای شما انجام میدهیم.
بنابراین، بهجای اینکه مجبور باشید مراحل دستی زیادی را انجام دهید، تنها با چند خط کد میتوانید عاملی بسازید.
و این دقیقاً همان کاری است که امروز میخواهیم انجام دهیم.
بنابراین، میخواهیم ببینیم این میاناهای برنامهسازی کاربردی واقعاً چگونه با برنامه نمونه ما Jetpacker کار میکنند.
اکنون، شما Jetpacker را در Google I/O و درطول این کنفرانس دیدهاید.
اما کاری که میتوانید انجام دهید این است که این رمزینه پاسخسریع را اسکن کنید و اگر علاقهمند هستید، میتوانید نگاهی به کد بیندازید، میتوانید نماینده خود را به کد ارجاع دهید، میتوانید با کد تعامل داشته باشید، و میتوانید درباره نحوه پیادهسازی همه این ویژگیها بیشتر بدانید.
اکنون Jetpacker یک دستیار سفر مدرن است که اساساً برای نمایش بسیاری از ویژگیهای هوشمند مختلف ساختهایم.
بنابراین، درحالحاضر از مدلهای دروندستگاهی و مدلهای ابری برای انواع میاناهای برنامهسازی کاربردی پیامواره استفاده میکند، اما آنچه اکنون قصد داریم بسازیم این است که میخواهیم یک عامل به آن اضافه کنیم.
و ما برای این عامل، برای Jetpacker، کاری که انجام میدهیم این است که از API عامل جدید ML Kit GenAI که تازه درباره آن صحبت میکردیم استفاده خواهیم کرد، و این API از سیستم AI Core زیربنایی که در دستگاههای Android اجرا میشود برای صحبت با مدل Gemini Nano استفاده خواهد کرد.
اکنون، من میتوانم هر چقدر که میخواهم صحبت کنم، و ما خیلی صحبت خواهیم کرد، اما، البته، ما میخواهیم این را به نمایش بگذاریم.
بنابراین، بیایید به تلفن برویم و این را بهصورت نمایشی نشان دهیم.
من اینجا یک Pixel 11 Pro دارم.
و فکر میکنم میتوانیم به Pro برویم و شما میتوانید آن را ببینید.
بفرمایید، این هم از این.
و همانطور که میبینید، برنامه Jetpacker را در اینجا درحال اجرا داریم.
در آن تعدادی از سفرهای آینده ما وجود دارد و میتوانم سفر فعلیمان را ببینم و در سفر فعلیمان میتوانید ببینید که امروز ۲۹ اکتبر است، بنابراین کارگاه هوش مصنوعی دروندستگاهی، نمایش نسخه نمایشی را داریم و فردا هم گشت دریایی در خلیج گلدن گیت و بازدید از موزه را در پیش داریم.
همه اینها عالی است.
اکنون میتوانیم به صفحه هزینهها برویم که در آن بودجه این سفر را پیگیری میکنیم.
و همانطور که در پایین سمت چپ میبینید، این درخشش کوچک جدید عاملگونه وجود دارد، و این همان عامل دروندستگاهی است که ما ساختهایم.
بنابراین، بیایید با یک فرمان بسیار ساده شروع کنیم.
فکر کنم میتوانیم از آن بخواهیم هزینه خاصی را اضافه کند.
مثلاً بگویید: هزینه تاکسی به Mountain View را اضافه کن، ۴۵ دلار بود.
بفرمایید.
درحال آماده کردن هزینه است و هزینه را اضافه کرد.
شگفتانگیز.
بسیار خوب، نسخه نمایشی ۱ کار کرد.
بیایید به اسلایدها برگردیم و ببینیم که این کار درواقع چگونه انجام شد.
بسیار خوب، اول از همه، همانطور که متوجه شدید، من از صدایم استفاده میکردم و آن صدا درواقع از مدل Gemini Nano بااستفاده از میانای برنامهسازی کاربردی تشخیص گفتار برای ترانویسی هرآنچه میگفتم به نوشتار استفاده میکرد.
پس آن نوشتار عالی است و میتوانیم آن را به مدل Gemini Nano ارسال کنیم.
اما چه کاری انجام میدهد؟
مثلاً به آن میگویم چیزی را به هزینهها اضافه کند، چیزی درباره ۴۵ دلار به آن میگویم، اما واقعاً نمیداند چگونه عمل کند، درست است؟
در این مرحله، فقط به من میگوید: «نمیدانم درباره چه چیزی صحبت میکنی.»
بنابراین، کاری که میخواهیم انجام دهیم این است که میخواهیم کمی به آن پیامواره اضافه کنیم.
میتوانیم ابزارهایی را به پیامواره اضافه کنیم، بنابراین میتوانیم انواع ابزارها را اضافه کنیم، در این مورد، من درحال اضافه کردن بودجه سفر، برنامه سفر، و غیره هستم. و اگر این را با میاناهای برنامهسازی کاربردی ما به مدل Nano منتقل کنید، بهطور خودکار متوجه میشود که بهجای برگرداندن نوشتار، اکنون میتواند فراخوانی تابع را برگرداند.
بنابراین، در مورد ما، درواقع میگوید: «خب، بهجای پاسخ دادن با نوشتار، هزینه ۴۵ دلاری تاکسی به مانتین ویو را اضافه میکنم.»
بسیار عالی، اما ویکتوریا، این چطور در عمل کار میکند؟
چگونه این را بسازیم؟
آره.
بنابراین، چگونه ابزارها را در کد خود تعریف میکنیم؟
بنابراین، این چیزی است که میخواهم توضیح دهم.
بنابراین، با Android MCP، از کتابخانه Jetpack AppFunctions برای تعریف و انتشار این ابزارها استفاده میکنیم.
بنابراین، کتابخانه AppFunctions میتواند سرویسها، دادهها، و کنشهای برنامهتان را در دفتر ثبت سیستمعامل Android نمایش دهد تا بتوانیم دقیقاً از همان توابع برای اعلام و توصیف ابزارها استفاده کنیم و از آنها در سراسر برنامهتان استفاده کنیم.
و این به کاربر اجازه میدهد این تکلیف را ازطریق عاملها و تعاملات سطح سیستم تکمیل کند.
بنابراین، میخواهم یک مثال عینی در کد به شما ارائه دهم.
/**
* Adds an expense to a trip.
*
* @param tripId Identifier of the trip (e.g., "paris-2026").
* @param amount Expense amount in dollars.
* @param description Short description of what the expense was for.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun addExpense(tripId: String, amount: Double, description: String) {
// Save expense to local DB / repository...
}بنابراین، میتوانید ببینید که این نمونهای از AppFunctionDeclaration است.
میتوانید ببینید که نظر KDoc در بالا، عملکرد تابع و پارامترهای آن را به زبان طبیعی توصیف میکند.
بنابراین، بهترین کار این است که واضحترین شرح ممکن را ارائه دهید زیرا قرار است این شرح را به مدل ارسال کنیم.
و سپس میتوانید در زیر آن، یک حاشیهنویسی، @AppFunctionAccessLevel را ببینید.
بنابراین، این کار رؤیتپذیری تابع را محدود میکند.
در این مثال، ACCESS_LEVEL_SELF را داریم، یعنی این توابعی که مینویسیم فقط برای برنامه شما، یعنی عاملهای برنامه شما، قابلمشاهده است.
و سپس، میتوانیم خود تابع تعلیق را تعریف و پیادهسازی کنیم و میتوانیم هر تعداد تابع برنامه که میتوانیم بنویسیم و همه آنها را در یک کلاس سرویس واحد گروهبندی کنیم.
بسیارخوب.
بنابراین، با این کار ما ابزارها را داریم و میتوانیم آن ابزارها را بنویسیم، این عالی است.
اما البته برای ساختن نماینده، به چیزی بیشتر از AppFunctions نیاز داریم.
بنابراین، سه بخش دیگر داریم که هنگام نوشتن یک عامل مهم است که درباره آنها بدانید.
اول از همه، ما درواقع داریم عامل را تعریف میکنیم.
بنابراین ما دستورالعملهای سیستم را تعریف میکنیم، ریلهای محافظ، پیکربندی، و نحوه کار با آن را تعریف میکنیم.
و سپس AgentRunner را هم داریم.
AgentRunner چیزی است که عامل را برای ما اجرا میکند.
بنابراین، این همان سازوکار حلقهای است که دربارهاش صحبت میکردیم.
و درنهایت، AgentEvents را داریم که روشی برای سیستم و API است تا به ما اطلاع دهد که درحال انجام چه کاری است.
بنابراین، وقتی نوشتار را برمیگرداند، وقتی فراخوانی ابزار را انجام میدهد، و غیره، به ما اطلاع میدهد تا بتوانیم آن را در واسط کاربر خود نمایش دهیم.
بسیارخوب.
بنابراین، بیایید به آن کد واقعی نگاه کنیم.
بسیارخوب.
قبلاز اینکه نوشتن کد را ادامه دهیم، ابتدا باید بررسی کنیم که آیا «فراخوانی تابع» در دستگاهتان دردسترس است یا نه، چون «فراخوانی تابع» فقط در Gemini Nano 4 پشتیبانی میشود.
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
// Device is supported!
} else {
// Graceful fallback
}ابتدا باید با فراخوانی مدل زایای getAvailableFeatures بررسی کنیم.
و بهسادگی میتوانیم بررسی کنیم که آیا «فراخوانی تابع» در ویژگیها فهرست شده است یا نه.
و اگر اینطور نباشد، میتوانیم یک جایگزین مناسب را پیادهسازی کنیم.
بسیارخوب.
بنابراین، اکنون، بیایید به کد بپردازیم.
if(FUNCTION_CALLING in features) {
// 1. Build the agent configuration
val agent = Agent.builder()
.setName("Assistant")
.setInstructions("You are a helpful on-device assistant.")
.enableAppFunctions()
.build()
// 2. Initialize the runner
val runner = AgentRunner(agent = agent)
// 3. Create your prompt
val prompt = content { text(userPrompt)}
// 4. Start the agent
runner.runSingleTurnAsync(prompt).textTokens().collect { responseMessage += it }
}میدانیم که «فراخوانی تابع» در دستگاه ما پشتیبانی میشود و سپس میتوانیم بهسادگی بااستفاده از Agent.builder، ساختن نماینده را شروع کنیم.
و سپس میتوانیم نام آن را تنظیم کنیم.
میتوانیم setInstructions تا بافت و شخصیت را تعریف کنیم.
و در آخر، با enableAppFunctions تماس میگیریم.
بنابراین، این تابع فراخوانی خواهد شد و بهطور خودکار همه AppFunctions را که قبلاً فهرست کردیم و عامل را ساختیم، کشف خواهد کرد.
سپس، باید AgentRunner را مقداردهی اولیه کنیم.
بنابراین، این عامل اجراکننده، حلقه اجرا را برای ما مدیریت خواهد کرد.
ما بهسادگی آن را با فراخوانی یک سازنده و ارسال عامل به آن فراخوانی میکنیم.
و درنهایت، میتوانیم فرمان صوتی ترانویسیشده را در یک بلوک محتوا قرار دهیم تا پیامواره ایجاد شود.
و سپس با دونده تماس میگیریم.
ما این تابع را runSingleTurnAsync در پیامواره مینامیم و این تابع عامل را راهاندازی میکند، استنتاج را اجرا میکند، و کل استنتاج هوش مصنوعی را انجام میدهد.
و اگر فقط بخواهیم پاسخ متنی نهایی مدل را در میانای کاربریمان جاریسازی کنیم، کافی است textTokens را فراخوانی کنیم تا پاسخ را بهمحض دریافت جمعآوری کند.
بنابراین، همین است.
این واقعاً فقط چند خط کد است، بنابراین وقتی این را گفتیم شوخی نمیکردیم.
بنابراین، فقط با همین چند خط کد، یک نماینده دارید.
آن عامل کاری را که میخواهید انجام خواهد داد.
این کار به ما پاسخ نوشتاری میدهد.
اما البته در این اولین نمایشی که نشان میدادیم، فقط نوعی فرمان بسیار ساده را انجام میدادیم، درست است؟
مثلاً من یک فرمان دادم و آن یک کنش انجام داد و تمام شد، بنابراین هنوز واقعاً یک عامل تمامعیار نیست، درست است؟
پس بیایید کمی هیجان به آن اضافه کنیم.
برای بخش بعدی، کاری که میخواهم انجام دهم این است که درواقع میخواهم فرمانی بدهم، اما میخواهم آن را کمی برای عامل سختتر کنم.
بنابراین، بیایید به دستگاه نمایشی من برگردیم و ببینیم آیا میتوانیم آن را کمی بیشتر، کمی سختتر کنیم.
ما ازقبل آنجا هستیم.
شگفتانگیز.
خب، همانطور که گفتم، برای فردا بازدید از موزه را برنامهریزی کردهایم.
و برای آن بازدید از موزه، اگر آن را باز کنم، میتوانید ببینید که قیمت ورودی ۲۵ دلار است.
اکنون بیایید ببینیم آیا عامل ما واقعاً قادر است خودش این موضوع را بفهمد یا نه.
بنابراین، به صفحه هزینههایم برمیگردم و به آن فرمانی میدهم و میگویم: «بلیت موزهام را به هزینههایم اضافه کن.»
بفرمایید.
و حالا، همانطور که میبینید، دارد برنامه سفرم را بررسی میکند، بازدید از موزه را پیدا میکند، و آن را به هزینههایم اضافه میکند.
بنابراین، در اینجا سه مرحله طول میکشد تا واقعاً به هدف خود برسد و یک پاسخ نوشتاری نهایی نیز به من داد.
اکنون، دوباره، همه اینها بهطور خودکار کار میکند.
سرعت واقعی آن مدل را در دستگاه دیدید.
بنابراین، بیایید به اسلایدها برگردیم و ببینیم که چگونه کار میکند.
بنابراین، همانطور که میبینید، ما چیزها را کمی نسبت به اولین نسخه نمایشی خود تغییر دادهایم.
در این مورد، همانطور که میبینید، من درباره موزه یا هزینه به آن چیزی نگفتم، بنابراین وقتی مدل پیامواره و ابزارها را دریافت کرد، سعی میکند تصمیم بگیرد که ابتدا چه کاری انجام دهد.
بنابراین، در اینجا، تصمیم میگیرد که، اوه، باید اطلاعات بیشتری درباره برنامه سفر فعلی من داشته باشد، بنابراین getItinerary را فراخوانی میکند.
پاسخی دریافت میکند و سپس متوجه میشود که، آهان، در این برنامه سفر بازدید از موزه وجود دارد، احتمالاً این همان چیزی است که او دربارهاش صحبت میکرد.
پس بیایید جزئیات آن رویداد خاص را پیدا کنیم.
و در میان همه فرادادههای دیگر آن رویداد خاص، میبیند که قیمت آن ۲۵ دلار است.
متوجه میشود، برمیگردد، و متوجه میشود که باید «افزودن هزینه» را فراخوانی کند.
بنابراین، در اینجا میبینیم که چندین مرحله یکی پساز دیگری اتفاق میافتد.
ویکتوریا، میتوانی کمی بیشتر دراینباره توضیح دهی؟
آره.
بنابراین، آنچه یولاندا توضیح داد، نمونهای واقعی از نمایشی است که او ارائه داد، اما من قصد دارم آن را به سطح مفهومیتر و انتزاعیتری ببرم.
بنابراین، این اتفاق هربار که «فراخوانی تابع» چندمرحلهای انجام میدهیم رخ خواهد داد.
بنابراین، اول از همه، این برنامه ما است و آن مدل Gemini Nano است.
و کاربر با ارسال پُرسمان و فهرست ابزارهای دردسترس به مدل شروع میکند.
بنابراین، مدل از این اطلاعات برای تصمیمگیری درباره آنچه باید برگرداند استفاده میکند.
و سپس، فرض کنید در حالت اول، یک «فراخوانی ابزار» برمیگرداند، بیایید آن را «فراخوانی ابزار ۱» بنامیم.
سپس، برنامه این «فراخوانی ابزار» را اجرا میکند و «پاسخ ابزار» را دریافت میکند.
سپس، آنچه به مدل برگردانده میشود درواقع سابقه کامل مکالمه است که شامل پُرسمان اصلی، فهرست ابزارها، اولین «فراخوانی ابزار»، و اولین «پاسخ ابزار» است که به مدل برگردانده میشود.
سپس مدل دوباره از این اطلاعات استفاده میکند تا تصمیم بگیرد که در مرحله بعد چه کاری انجام دهد.
بنابراین، این روند میتواند تا زمانی که به نتیجه نهایی برسیم ادامه داشته باشد.
بنابراین، فرض کنید مدل nاُمین «فراخوانی ابزار» را برمیگرداند و سپس برنامه دوباره کل سابقه مکالمه را برمیگرداند.
و وقتی مدل راضی شد، یعنی میداند که پُرسمان برآورده شده است، یک «پاسخ نهایی» به برنامه ارسال میکند و سپس برنامه میتواند در واسط کاربر نشان دهد که کار انجام شده است.
بسیارخوب.
بنابراین، اکنون میخواهم توضیح دهم که چگونه این را در کد پیادهسازی میکنیم.
/**
* Retrieves the itinerary events (ID, title, location) for a trip.
* Note: Call getEventDetails(eventId) to retrieve ticket prices or notes.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getItinerary(tripId: String): List<EventSummary> { ... }
/**
* Retrieves details for a specific event, including admission ticket prices.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getEventDetails(eventId: String): EventDetails { ... }بنابراین، مثال کد تکمرحلهای را بهخاطر دارید و این کد چندمرحلهای است، بنابراین بسیار شبیه بهنظر میرسد و مهمترین نکته این است که KDoc را واقعاً واضح بنویسید.
بنابراین، در این مثال، تابع getItinerary بهصراحت میگوید که فقط خلاصه سطح بالایی از رویدادها را برمیگرداند.
بنابراین، همچنین اشاره میکنیم که اگر بخواهیم جزئیات سفر خاصتری داشته باشیم، باید getEventDetails را با eventId فراخوانی کنیم.
و سپس، getEventDetails همچنین دارای نظر KDoc است، میگوید که میتواند جزئیاتی مانند قیمت بلیت و یادداشتهای دیگر را بازیابی کند.
بنابراین، بااستفاده از نظر، با توصیف واضح نظر، میتوانیم مدل را راهنمایی کنیم تا بداند ابزارها چگونه با یکدیگر تعامل دارند.
بسیارخوب.
بنابراین، این نحوه ساختن عامل برای انجام این حلقه، این حلقه عاملگرا است.
val agent = Agent.builder()
.setName("Assistant")
.setInstructions("You are a helpful travel assistant. Active tripId: $tripId")
.enableAppFunctions()
.setMaxTurns(5) // Guardrail against infinite tool loops
.build()
val runner = AgentRunner(agent = agent)
// Automatically loops through all 3 tool calls in a single user turn!
runner.runSingleTurnAsync(prompt).collect { event ->
when (event) {
is AgentEvent.FunctionCall -> showToolRunning(event.name)
is AgentEvent.FunctionResponse -> showToolCompleted(event.name)
is AgentEvent.TextMessage -> responseMessage += event.text
}
}این مثال بسیار شبیه به مثال «تکمرحلهای» است، فقط دو مورد اضافه شده است.
اولین مورد این است که باید حداکثر نوبتها را تنظیم کنیم و در این مثال، فقط پنج نوبت تنظیم میکنیم.
پس، چه کاری انجام میدهد؟
بنابراین، این کار از تکرار بیپایان مدل جلوگیری میکند.
فرض کنید پیامواره مبهمی دارد یا شاید مدام بین فراخوانی یک ابزار به ابزار دیگر و سپس برگشتن به آن و رفتن به ابزار دیگر در رفتوآمد است، بنابراین، اگر این را تنظیم کنیم، متوقف میشود و به حلقه ادامه نمیدهد.
و سپس، همچنان همان تابع را برای اجرا کردن آن فراخوانی میکنیم، runSingleTurnAsync.
بنابراین، اگرچه به آن تکنوبتی گفته میشود، اما درواقع یک نوبت بهمعنای یک پرسمان کاربر است.
So, the expectation from the end-user is that I send one query, you return one answer, without really, really caring into much detail like what happened under the hood, like how many time it's looping, and so on.
بنابراین، میتوانیم همچنان از همان تابع استفاده کنیم.
و سپس، ما همچنین، چون ابزار فراخوانی ابزار چندمرحلهای کمی بیشتر زمان میبرد، شما همچنین در نسخه نمایشی دیدید که درواقع نشان میدهد درحالحاضر چه تابعی را فراخوانی میکند، درحالحاضر چه چیزی درحال اجرا است، و آیا انجام شده است یا نه.
بنابراین میخواهیم پیشرفت زنده را در میانای کاربر نشان دهیم.
بنابراین، بهجای اینکه فقط پاسخ نوشتاری را جمعآوری کنیم، میتوانیم نام ابزار، event.name، را هروقت که AgentEvent یک FunctionCall یا FunctionResponse صادر میکند نمایش دهیم، و وقتی کار تمام شد، میتوانیم فقط TextMessage را مثل قبل برگردانیم.
شگفتانگیز!
بنابراین، ما این اجرای ناهمگام را اجرای ناهمگام تکنوبتی مینامیم که البته بهنوعی فرض میکند نوع دیگری از میانای برنامهسازی کاربردی نیز وجود دارد، درست است؟
آره.
بنابراین، البته که فقط یک مرحله کافی نیست.
میخواهیم چندمرحلهای انجام دهیم.
پس بیایید جلسات چندنوبتی را انجام دهیم و فکر میکنم میتوانیم به نسخه نمایشی من برگردیم و آن را کمی سختتر کنیم.
داشتیم درباره بازدید از موزه صحبت میکردیم، اما گشت دریایی خلیج گلدن گیت را هم داشتیم.
بنابراین برای آن یکی، میخواهم قبلاز اینکه واقعاً آن را رزرو کنم، ببینم آیا بودجه کافی برای آن دارم یا نه.
پس بیایید سؤالی بپرسیم.
ببینیم.
بنابراین فکر میکنم فردا تیمم را به گشت دریایی خلیج گلدن گیت ببرم و فکر میکردم حدود ۱۸۰ دلار باشد.
پس اگر این کار را انجام دهم، بودجه باقیمانده من واقعاً چقدر خواهد بود؟
پس چه کار میکند؟
به این سؤال پاسخ میدهد که بودجه باقیمانده من چقدر خواهد بود.
بنابراین برنامه سفر فعلی را درک میکند.
این اطلاعات را دارد و میتواند آن را بگوید.
اما بعداً میتوانم سؤال تعقیبی هم در اینجا بپرسم.
بسیار خوب، عالی است.
میتوانی آن را به هزینههایم اضافه کنی؟
بسیار خوب، حالا ادامه میدهیم.
اوه، باید به ارز تبدیل شود، خیلی جالب است، جلوه نمایشی.
بسیارخوب، پس به دلار است.
۱۸۰ دلار.
بفرمایید.
این اولینباری است که این اتفاق میافتد، خیلی جالب است.
بسیار خوب، پس چه کاری انجام میدهد؟
به من میگوید که سه نوبت طول کشیده و به هزینهها اضافه شده است و همچنین به من اجازه میدهد این جلسه را بازآوری کنم.
بنابراین یک دکمه بازآوری در بالا دارم و اگر روی آن کلیک کنم، کل سابقه را پاک میکنم و میتوانم از اول شروع کنم.
بنابراین، فکر میکنم میتوانیم به اسلایدهایمان برگردیم.
ما نسخههای نمایشی فعال را داریم، همه آنها کاری را که میخواهیم انجام میدهند و بنابراین فکر میکنم کاری که میتوانیم انجام دهیم این است که میتوانیم به کد برگردیم.
آره.
این واقعاً خیلی جالب است.
بنابراین متوجه میشوید که یولاندا اشارهای نکرده است، فقط میگوید، اوه، به دلار است، او نمیگوید که مثلاً برای بلیت گشت دریایی خلیج گلدن گیت است.
پس مدل چگونه از همه بافت مطلع میشود؟
بنابراین این کمی متفاوت از چیزی است که در دو تکهکد قبلی به شما نشان دادم.
// 1. Get or create an isolated session for the current trip
val session = runner.getOrCreateSession(tripId)
// 2. Run a turn (history is preserved automatically across turns)
session.runAsync(prompt).collect { event ->
when (event) {
is AgentEvent.FunctionCall -> showToolRunning(event)
is AgentEvent.FunctionResponse -> showToolCompleted(event)
is AgentEvent.TextMessage -> responseMessage += event.text
}
}
// 3. Clear history when the user taps "New conversation"
session.delete()بنابراین بهجای استفاده از AgentRunner و runSingleTurnAsync، درواقع باید یک جلسه ایجاد کنیم.
بنابراین در اینجا runner.getOrCreateSession را با tripId فراخوانی میکنیم.
پس چرا باید جلسه ایجاد کنیم؟
چون runSingleTurnAsync حافظه را بازنشانی میکند، بنابراین بافت نخواهد داشت.
نمیتوانیم مکالمه چندنوبتی داشته باشیم.
بنابراین وقتی میخواهید مکالمه چندنوبتی داشته باشید، از جلسه، یعنی جلسه نماینده، استفاده میکنیم.
و سپس tripId را منتقل میکنیم.
چرا باید این کار را انجام دهیم؟
چون قبلاً آن را دیدهاید، برنامه Jetpacker، چندین سفر را مدیریت میکند.
یک سفر ADS، یک سفر پاریس، شاید یک سفر توکیو وجود دارد.
بنابراین میخواهیم بافت را در سفر حفظ کنیم.
نمیخواهیم با هم درگیر شویم، میدانی؟
بنابراین، بهجای فراخوانی runSingleTurnAsync، runAsync را داریم.
پس این متفاوت است.
بنابراین این روی جلسه اجرا میشود و سپس ما هنوز همان پیامواره را داریم.
و سپس همچنان میتوانیم AgentEvent، FunctionCall، FunctionResponse، و TextMessage را مانند قبل منتشر کنیم، همان است.
و سپس وقتی یولاندا روی دکمه مکالمه جدید، دکمه بازآوری، کلیک کرد، درواقع میتوانیم session.delete داشته باشیم.
آره.
بسیارخوب.
بنابراین ما همه این کارها را با میاناهای برنامهسازی کاربردی عاملی جدید انجام دادیم و این شگفتانگیز است و برای بسیاری از موارد استفاده شما بهطور شگفتانگیزی کار خواهد کرد.
اما البته، ما همیشه میخواهیم چیزهای سریع بسازیم، یا چیزهای آسان را ممکن کنیم، اما همچنین میخواهیم موارد پیچیدهتر را نیز برای شما ممکن کنیم.
بنابراین میخواهیم مطمئن شویم که اگر نیاز داشتید، همچنان بتوانید از میاناهای برنامهسازی کاربردی دسترسی مستقیم برای ساختن نسخه سطح پایینتری از این موارد استفاده کنید.
پس شاید ویکتوریا، شما بتوانید توضیح دهید که این به چه معناست و چه زمانی دوباره به این سطح دسترسی مستقیم برمیگردیم؟
بله، حتماً، یولاندا.
بنابراین، این دسترسی مستقیم، ممکن است ازقبل با Prompt API آشنا باشید.
بنابراین، در اینجا از فراخوانی تابع نیز پشتیبانی کردیم و دو مورد استفاده داریم که ترجیح میدهیم بهجای جریان عاملی آسانتر، از این API استفاده کنیم.
بنابراین، مورد اول یک مورد استفاده ساده است، یک مورد استفاده بسیار ساده.
بنابراین بهخاطر دارید که در جریان عاملگرا، کل مکالمه، پُرسمان، ابزارها، فراخوانی ابزار، پاسخ ابزار، و همه چیز را به مدل برمیگردانیم.
و شاید برخی موارد استفاده وجود داشته باشد که ما نمیخواهیم این کار را انجام دهیم، مثلاً فقط میخواهیم پیامواره را ارسال کنیم.
بنابراین فرض کنید میخواهیم یک پیامواره به مدل ارسال کنیم که با یک فراخوانی ابزار برمیگردد، و سپس نمیخواهیم فراخوانی ابزار را برگردانیم، فقط میخواهیم پیامواره دیگری به مدل ارسال کنیم، بنابراین میتوانیم سابقه را در آنجا ویرایش کنیم، گویی کنترل کامل بر سابقه داریم.
در اینجا مثال ملموستری برایتان میآورم.
// Turn 1: Execute function directly without a second model call
val turn1 = content(Content.Role.USER) {
text("Change the background to a sunset")
}
val response1 = generativeModel.generateContent(
generateContentRequest(turn1) { setTools(tools) }
)
executeFunctionCalls(response1)
// Turn 2: Send only user prompts to save context tokens
val customHistory = listOf(
turn1,
content(Content.Role.USER) { text("Oh no, I meant a beach") }
)
val response2 = generativeModel.generateContent(
GenerateContentRequest.Builder(customHistory).setTools(tools).build()
)فرض کنید برنامهای داریم که میتواند زمینه پسزمینه دستگاهتان را تغییر دهد.
و فرض کنید کاربر این پُرسمان را ارسال میکند: «پسزمینه را به غروب آفتاب تغییر بده.»
و سپس، بله، برنامه ما این تابع را اجرا کرد، پسزمینه به غروب آفتاب تغییر کرد.
و سپس، کاربر نظرش را تغییر داد و گفت: «اوه، نه، منظورم ساحل بود.»
و اتفاقی که در اینجا میافتد این است که نیازی نیست وضعیت کامل را ارسال کنیم که دستگاه قبلاً پسزمینه را به غروب آفتاب تغییر داده است و اکنون میخواهد آن را به ساحل تغییر دهد.
نه، لازم نیست این کار را انجام دهیم.
فقط باید پیامواره اول و پیامواره دوم را ارسال کنیم، زیرا پیامواره اول حاوی اطلاعات پسزمینه است.
بنابراین متوجه میشود که، اوه، این یعنی میخواهد پسزمینه را به ساحل تغییر دهد.
بنابراین میتوانیم از Prompt API برای انجام این کار استفاده کنیم.
ما کنترل کامل روی سابقه داریم، دادهواحد بافتی را در دستگاه ذخیره میکنیم، چون، میدانید، دستگاه پنجره دادهواحد محدودتری دارد، و با این کار میتوانیم دادهواحدهای بیشتری ذخیره کنیم.
و مورد استفاده دوم، در واقع نقطه مقابل آن است.
بنابراین اگر موارد استفاده پیچیدهای داریم که Agentic API هنوز نمیتواند از آنها پشتیبانی کند، مثلاً اگر سیستمهای چندعاملی داریم که در آنها چندین عامل فرعی برای حل کردن تکلیف پیچیده با یکدیگر تعامل دارند، یا سیستم ترکیبی که در آن هم مدلهای دروندستگاهی داریم و هم میخواهیم مدلهای ابری داشته باشیم، یا اگر میخواهیم حافظه سفارشی و فشردهسازی بافت داشته باشیم.
بنابراین این به سابقه مربوط میشود، مثل کنترل کامل سابقه که قبلاً به آن اشاره کردم.
بنابراین فرض کنید میخواهیم، چون پنجره بافت محدود است، پس نمیخواهیم همه چیز را به مدل برگردانیم، فقط میخواهیم مهمترین بخش، یا شاید جدیدترین بخش را خلاصه کنیم، بنابراین کنترل کامل روی این داریم، سپس میرویم و از میانای برنامهسازی کاربردی دسترسی مستقیم استفاده میکنیم.
و برای این موارد استفاده، میتوانیم از چارچوبهایی مانند Agent Development Kit یا ADK یا Crew استفاده کنیم، یا میتوانید چارچوب هماهنگسازی سفارشی خودتان را ایجاد کنید.
بسیارخوب.
این همه چیزی بود که درباره agentic API داشتیم.
اما یک چیز دیگر هم هست که میخواستیم به آن اشاره کنیم.
So we've been talking about in-app agents all the time, so you're building your own agentic experience inside your own app, but we also were talking about how we're using AppFunctions, and why would you be using AppFunctions if the only thing you're doing is inside your own app?
خب، میدانیم که توسعهدهندگانی مثل شما واقعاً دوست ندارند کارها را دو بار انجام دهند، درست است؟
ما دوست نداریم کد را کپی و جایگذاری کنیم، دوست نداریم مجبور باشیم منطق کسبوکار را تکرار کنیم.
بنابراین با AppFunctions، بهجای اینکه فقط به عامل درونبرنامه خود اجازه دهیم آنها را مصرف کند، با یک تغییر بسیار ساده، میتوانیم ابزارهای خود را برای عوامل سیستم نیز دردسترس قرار دهیم.
پس با کد چطور بهنظر میرسد؟
آره.
بنابراین، با AppFunction، فقط کافی است یکبار AppFunctions را بنویسیم و سپس میتوانیم آنها را در سطوح مختلف لایهها بهدلخواه خودمان نمایش دهیم.
بهیاد دارید که قبلاً ACCESS_LEVEL_SELF را داشتیم که فقط عملکردهایی را که نوشته بودیم برای عامل درونبرنامهای ما آشکار میکرد، اما میتوانیم آن سطح دسترسی را به ACCESS_LEVEL_ANDROID_TRUSTED نیز تغییر دهیم.
و این کار باعث میشود توابع دراختیار عاملهای مورداعتماد در سیستمعامل Android قرار بگیرد.
بنابراین نیازی به بازنویسی توابع، فرمانها، یا منطق پایگاه داده نیست.
AppFunctions درحالحاضر بهعنوان صدای برنامه شما هم بهصورت داخلی و هم بهصورت خارجی عمل میکند.
ما هم میخواهیم نمایشی از آن داشته باشیم، درست است؟
بسیار خوب، بیایید به تلفن برگردیم و برای آخرین نسخه نمایشی، از برنامه Jetpacker خارج شویم.
بنابراین میتوانیم خارج شویم و درواقع وارد Gemini شویم.
بنابراین در Gemini، میتوانیم @Jetpacker را صدا بزنیم.
نکتهای که باید به آن توجه کنید این است که این ویژگی فقط بهصورت نسخه بتای محدود دردسترس است، بنابراین امروز برای همه دردسترس نخواهد بود، اما امکان آزمایش و امتحان کردن آن وجود دارد.
بنابراین با Jetpacker، بیایید ببینیم.
چه کاری باید انجام دهیم؟
اوه، امروز صبح قهوه خوردم، درست است؟
میتوانی یک قهوه ۷ دلاری به هزینههای سفر ADS اضافه کنی؟
بفرمایید.
و چیزی که میبینید این است که به برنامه Jetpacker متصل میشود.
و این هم از این.
هزینه را برای من اضافه کرد.
بنابراین اساساً دیدهایم که چگونه میتوانیم از همه چیز در Jetpacker استفاده کنیم.
نکته جالب درباره Jetpacker این است که میتوانیم حالت هواپیما را هم روشن کنیم.
بنابراین ما واقعاً آن را امروز نشان ندادهایم، اما میتوانیم حالت هواپیما را روشن کنیم، و هر چیزی که در نسخههای نمایشی دیدید، و من میخواهم آن را به نمایش بگذارم، زیرا میخواهم مطمئن شوم که همه شما به من اعتماد دارید، پس بیایید Wi-Fi را خاموش کنیم.
کسی هست که نمونه نمایشی خوبی داشته باشد، مثلاً هزینهای که بتوانم اضافه کنم؟
ببینیم.
شاید بتوانیم «این چیست؟» را انجام دهیم.
اوه، اجاره خودرو خوب است، نه؟
بسیارخوب.
بیایید این را امتحان کنیم: «هزینه ۳۰۰ دلاری برای اجاره خودرو اضافه کن».
بسیار خوب، متوجه شد.
بسیارخوب.
شگفتانگیز.
بسیارخوب، بیایید به اسلایدها برگردیم.
بنابراین درباره AppFunctions صحبت میکردیم.
میدانم که ما به آن-- خیلی خیلی مختصر اشاره کردیم.
بنابراین اگر میخواهید بیشتر بدانید، صحبت دیگری از دن و سارا وجود دارد که با جزئیات بسیار بیشتری توضیح میدهند که چگونه میتوانید این ویژگی را فعال کنید، چگونه میتوانید آن را آزمایش کنید، چه ابزارهایی داریم و غیره.
بنابراین امروز ما به نوعی چیزهای مختلف زیادی را پشت سر گذاشتیم.
با «تکمرحلهای»، «چندمرحلهای»، و «چنددورهای» آشنا شدیم و درنهایت دیدیم که چگونه میتوانید از System AppFunction استفاده کنید.
اکنون، با این فکر میکنم چند نکته قبلاز رفتن داشته باشیم.
آره.
پس این سه نکته را بهخاطر بسپارید وقتی که شروع به ساختن این تجربههای عاملی میکنیم.
بنابراین اولین مورد این است که مجموعه ابزارها را متمرکز نگه دارید.
بنابراین بهخاطر داشته باشید که مدلهای دروندستگاهی بهدلیل مقیاس کوچکترشان قابلیتهای محدودی دارند.
بنابراین، بهجای ثبت دهها ابزار، بهتر است تعداد کمتری ابزار را انتخاب کنید.
و سپس همانطور که قبلاً گفتم، شرحهای KDoc واضح بنویسید زیرا مدل از آنها برای تصمیمگیری درباره آنچه قرار است برگرداند استفاده میکند.
و سپس میتوانیم و باید یک محافظ حداکثر نوبت نیز تنظیم کنیم.
این کار همچنین از تکرار بیپایان مدل درصورت سردرگم شدن جلوگیری میکند.
این یعنی ممکن است باتری کاربر را خالی کنیم.
بنابراین اگر این را داشته باشیم، میتوانیم در مصرف باتری کاربر صرفهجویی کنیم.
و درنهایت باید دردسترس بودن را بررسی کنیم تا ببینیم آیا «فراخوانی تابع» ازقبل در دستگاه پشتیبانی میشود یا نه، و یک جایگزین مناسب پیادهسازی کنیم.
برای مثال، اگر پشتیبانی نمیشود، میتوانید به مدلهای ابری یا میانای کاربر استانداردتان مراجعه کنید.
بسیارخوب، فکر میکنم میتوانیم این موضوع را با منابع خودمان حل کنیم.
بنابراین یک بار دیگر، رمزینههای پاسخسریع را به شما نشان میدهم.
لطفاً نگاهی بیندازید.
فکر میکنم یکی از چیزهایی که باید بهخاطر داشته باشیم این است که ما «مهارتهای Android» را هم برای این کار داریم.
بنابراین لازم نیست بروید و واقعاً در تکتک جزئیات ریز و درشت عمیق شوید.
ما مستندات کامل و یک «مهارت Android» داریم که با این API عاملی جدید همراه است.
پس نگاهی به آن بیندازید.
«پیشنمای توسعهدهندگان نرمافزار AICore» را بارگیری کنید تا بتوانید همه این موارد را آزمایش کنید، و کدنویسی خوبی داشته باشید.
/**
* Adds an expense to a trip.
*
* @param tripId Identifier of the trip (e.g., "paris-2026").
* @param amount Expense amount in dollars.
* @param description Short description of what the expense was for.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun addExpense(tripId: String, amount: Double, description: String) {
// Save expense to local DB / repository...
}val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
// Device is supported!
} else {
// Graceful fallback
}if(FUNCTION_CALLING in features) {
// 1. Build the agent configuration
val agent = Agent.builder()
.setName("Assistant")
.setInstructions("You are a helpful on-device assistant.")
.enableAppFunctions()
.build()
// 2. Initialize the runner
val runner = AgentRunner(agent = agent)
// 3. Create your prompt
val prompt = content { text(userPrompt)}
// 4. Start the agent
runner.runSingleTurnAsync(prompt).textTokens().collect { responseMessage += it }
}/**
* Retrieves the itinerary events (ID, title, location) for a trip.
* Note: Call getEventDetails(eventId) to retrieve ticket prices or notes.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getItinerary(tripId: String): List<EventSummary> { ... }
/**
* Retrieves details for a specific event, including admission ticket prices.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(level = ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
suspend fun getEventDetails(eventId: String): EventDetails { ... }val agent = Agent.builder()
.setName("Assistant")
.setInstructions("You are a helpful travel assistant. Active tripId: $tripId")
.enableAppFunctions()
.setMaxTurns(5) // Guardrail against infinite tool loops
.build()
val runner = AgentRunner(agent = agent)
// Automatically loops through all 3 tool calls in a single user turn!
runner.runSingleTurnAsync(prompt).collect { event ->
when (event) {
is AgentEvent.FunctionCall -> showToolRunning(event.name)
is AgentEvent.FunctionResponse -> showToolCompleted(event.name)
is AgentEvent.TextMessage -> responseMessage += event.text
}
}// 1. Get or create an isolated session for the current trip
val session = runner.getOrCreateSession(tripId)
// 2. Run a turn (history is preserved automatically across turns)
session.runAsync(prompt).collect { event ->
when (event) {
is AgentEvent.FunctionCall -> showToolRunning(event)
is AgentEvent.FunctionResponse -> showToolCompleted(event)
is AgentEvent.TextMessage -> responseMessage += event.text
}
}
// 3. Clear history when the user taps "New conversation"
session.delete()// Turn 1: Execute function directly without a second model call
val turn1 = content(Content.Role.USER) {
text("Change the background to a sunset")
}
val response1 = generativeModel.generateContent(
generateContentRequest(turn1) { setTools(tools) }
)
executeFunctionCalls(response1)
// Turn 2: Send only user prompts to save context tokens
val customHistory = listOf(
turn1,
content(Content.Role.USER) { text("Oh no, I meant a beach") }
)
val response2 = generativeModel.generateContent(
GenerateContentRequest.Builder(customHistory).setTools(tools).build()
)