Yazılım Mühendisi
Cihaz üzerinde otonom temsilciler oluşturarak Android yapay zeka özelliklerinizi bir üst seviyeye taşıyın. Bu oturumda, ML Kit'in doğrudan model erişimi API'leri üzerine inşa edilen yeni temsilci API'lerini inceleyeceğiz.
Açık kaynaklı Jetpacker uygulamasını kullanarak yerel verilerde çok adımlı işlemleri zincirlemeyi, izole bağlamlarla durum bilgisi olan çok turlu oturumları yönetmeyi ve tek bir kod tabanıyla hem uygulama içi asistanları hem de Android sistem düzeyindeki yüzeyleri desteklemek için Android MCP'yi bağlamayı öğreneceksiniz.
AgentRunner, araç yürütmeyi koordine eder ve bağlamı kaybetmeden çok turlu görüşmeler için oturum yöntemleri sağlar.Transkript
Adım Jolanda Verhoef.
Android'de Geliştirici İlişkileri mühendisiyim. Bugün AICore mühendislik ekibinden Victoria Lestari de bize katılıyor.
Herkese merhaba.
Bugün bize katıldığınız için teşekkür ederiz.
Bu nedenle ekibimiz, en son teknoloji yapay zeka modellerini buluttan çıkarıp doğrudan Android cihazlarda çalıştırmak için yıllardır yakın bir işbirliği içinde çalışıyor.
Bu çalışmalar, ML Kit GenAI paketi altında gruplandırılır.
Bu ML Kiti GenAI paketinde, anahtar teslim çözümler için üst düzey API'ler bulunur. Bunlar arasında metin özetleme, resim açıklaması ve konuşma tanıma gibi özellikler yer alır.
Öte yandan, cihaz üzerinde çalışan Gemini Nano modeline doğrudan erişim de sağladık.
Bugün ise bir adım daha ileri gidiyoruz.
Bu doğrudan erişim sayesinde, uygulama içi temsilciler oluşturmanıza olanak tanıyacağız. Bu temsilciler, birden fazla adımda akıl yürütebilir, sohbet bağlamını kaydedebilir ve uygulamanızda işlem yapabilir.
Peki, bununla tam olarak neyi kastediyoruz Victoria?
Evet.
Cihaz üzerinde kullanım alanlarından bahsederken, olası bir dolandırıcılığı tespit etmek veya bir telefon görüşmesini özetlemek gibi daha kısa, daha basit ve bağımsız görevleri kastediyoruz.
Fotoğraf kataloğunuz hakkında soru sorma veya Google Haritalar'a soru sorma gibi daha karmaşık bir şey oluşturmak istediğimizde ise doğal olarak büyük bulut modellerine başvururuz.
Bulut modelleri, muazzam işlem gücü ve gerçek zamanlı veri avantajı sunar. Ancak cihaz üzerinde yapay zeka, düşük gecikme süresi, gerçek zamanlı ve yerel veri işleme için yeni olanaklar sağlar.
Gemini Nano, cihazlarınızda yapabileceklerinizi şimdiden artırdı.
Örneğin, Pixel Görsel Notlar ile yerel ekran görüntülerinde arama yapabilir ve bunlarla ilgili sorular sorabilirsiniz.
Temsilcileri cihaz üzerinde çalıştırdığımızda model kapasitesinde büyük bir sıçrama yapmamız gerekiyor. Bu nedenle, adımlar arasında akıl yürütebilen ve gerçek eylemler gerçekleştirebilen Gemini Nano 4'ü geliştiriyoruz.
Ancak bu yeni özellik, yalnızca geliştiricilerin hedefleyebileceği gerçek cihazlar varsa önemlidir.
Gemini Nano 4, belirli amiral gemisi cihazlarda kullanıma sunuldu. Ayrıca AICore Geliştirici Önizlemesi'ni kullanarak daha birçok cihazda test edebilirsiniz.
Önümüzdeki yılın başlarında 100 milyon cihaza ulaşma hedefimize de yaklaşıyoruz.
Peki, bu ne anlama geliyor?
Yani bu deneyimleri oluşturmaya ve test etmeye hemen başlayabilirsiniz. Bugün oluşturduğunuz deneyimler, gelecekte devasa bir kitleye ulaşacak.
Gemini Nano 4'ten ve potansiyel erişiminden bahsettik. Şimdi de ajan tabanlı yeteneklerin ne anlama geldiğini konuşalım.
Bu sayede, temsilci yapay zeka karmaşık görevleri planlayabilir, bu görevleri birden fazla adıma bölebilir ve ardından bu adımları uygulamanızda yürütebilir.
Örneğin, cihaz üzerindeki Gemini Nano modelimiz yerel veritabanınızı sorgulayabilir, arka uçtan veri alabilir, karmaşık sorularınızı değerlendirip yanıtlayabilir ve cihazınızda yerel uygulama iş akışını tetikleyebilir.
Peki, yapay zeka plan yapmaktan gerçek eylemlere nasıl geçiyor?
Bugünkü konumuz ise işlev çağrısı.
Ajan tabanlı yeteneklerin temel motorudur.
Doğal dili sistem yürütme diline çevirir ve karmaşık görevleri çözmek için gereken yerel API'leri seçip çalıştırarak modeli pasif akıl yürütmeden aktif yürütmeye geçirir.
Ayrıca, işlev çağrısının Gemini Nano 4'te yerel olarak desteklendiğini duyurmaktan mutluluk duyuyoruz.
Şu anda AICore Geliştirici Önizlemesi'nde kullanılabilen bu özellik, yakında Nano 4'ü destekleyen tüm cihazlarda kullanıma sunulacak.
Modelde bu tür özelliklerin olması harika ancak modeldeki bir özellik, geliştirici olarak size henüz API'leri sağlamaz.
Bu nedenle, temsilci oluşturmayı gerçekten kolaylaştırmak için ML Kit GenAI paketine doğru API'leri eklemek üzerinde de çalışıyoruz.
Biz de mevcut doğrudan erişim API'lerimizle başladık ve bunlara işlev türleri ile görüşme geçmişi türleri ekledik.
ML Kit'in Prompt API'sine eklenen bu iki yeni özellik sayesinde artık bu düşük seviyeli ajan tabanlı yetenekleri uygulamanıza ekleyebilirsiniz.
Ancak bu noktada kendiniz yapmanız gereken çok şey vardır.
Bu nedenle, işlevleri manuel olarak yürütmeniz, çağrı geçmişini kaydetmeniz ve birden fazla çağrı için kendinizi döngüye almanız gerekir.
Yani birden fazla çağrı yapmanız gerekiyorsa bir tür döngü sistemi oluşturmanız ve sistem için kendi güvenlik önlemlerinizi oluşturmanız gerekir.
Bu mümkün ancak elbette bu işlemi hızlı ve kolay bir şekilde yapmak istiyoruz.
Bu nedenle, bu Prompt API kitaplığının yanı sıra yeni bir temsilci akışları kitaplığı da ekledik.
com.mlkit.genai:agent-api ile bulabilirsiniz. Şu anda alfa sürümünde kullanılabilir.
Bu yeni API sayesinde işin zor kısmını sizin için biz yapıyoruz.
Böylece, birçok manuel adım uygulamak zorunda kalmadan yalnızca birkaç satır kodla bir temsilci oluşturabilirsiniz.
Bugün tam olarak bunu yapacağız.
Bu API'lerin örnek uygulamamız Jetpacker ile nasıl çalıştığını göreceğiz.
Jetpacker'ı Google I/O'da ve bu konferans sırasında görmüşsünüzdür.
Ancak bu QR kodunu tarayabilir ve ilgileniyorsanız koda göz atabilir, temsilcinizi koda yönlendirebilir, kodla etkileşim kurabilir ve tüm bu özellikleri nasıl uyguladığımız hakkında daha fazla bilgi edinebilirsiniz.
Jetpacker, birçok farklı akıllı özelliği sergilemek için geliştirdiğimiz modern bir seyahat asistanıdır.
Yani her türlü istem API'si için cihaz üzerinde ve bulut modellerini zaten kullanıyor. Ancak şimdi buna bir temsilci ekleyeceğiz.
Jetpacker adlı bu temsilci için az önce bahsettiğimiz yeni ML Kit GenAI Agent API'yi kullanacağız. Bu API, Gemini Nano modeliyle iletişim kurmak için Android cihazlarda çalışan temel Çekirdek Yapay Zeka sistemini kullanacak.
Şimdi, istediğim kadar konuşabilirim ve oldukça fazla konuşacağız ancak elbette bunu sergilemek istiyoruz.
Şimdi telefona geçelim ve bunu demo olarak gösterelim.
Elimde bir Pixel 11 Pro var.
Pro'ya geçebiliriz. Bu sayede, Pro'yu görebilirsiniz.
İşte oldu.
Gördüğünüz gibi Jetpacker uygulaması burada çalışıyor.
Burada yaklaşan gezilerimiz var. Mevcut gezimize bakabilirim. Mevcut gezimizde, şu anda 29 Ekim olduğunu görebilirsiniz. Cihaz üzerinde yapay zeka atölyemiz, demo vitrinimiz var. Yarın da Golden Gate Bay Cruise ve müze ziyaretimiz var.
Bunların hepsi çok güzel.
Şimdi bu seyahatin bütçesini takip ettiğimiz gider ekranına gidebiliriz.
Sağ altta gördüğünüz gibi, yeni bir küçük temsilci parıltısı var. Bu, geliştirdiğimiz cihaz üzerinde temsilci.
Oldukça basit bir komutla başlayalım.
Belirli bir harcamayı eklemesini isteyebiliriz.
Örneğin, "Mountain View'a taksiyle gitmek için 45 dolarlık bir harcama ekle" diyebilirsiniz.
İşte başlıyoruz.
Gideri hazırlıyor ve ekledi.
Harika.
İlk demo başarılı oldu.
Slaytlara dönüp bunun nasıl çalıştığına bakalım.
Öncelikle, fark ettiğiniz üzere sesimi kullanıyordum. Bu ses, konuşma tanıma API'sini kullanarak söylediklerimi metne dönüştürmek için Gemini Nano modelini de kullanıyor.
Bu metin, Gemini Nano modeline gönderilmeye hazır hale gelir.
Peki ne yapacak?
Örneğin, giderlere bir şey eklemesini söylüyorum, 45 dolarla ilgili bir şey söylüyorum ama nasıl davranacağını tam olarak bilmiyor, değil mi?
Bu noktada bana yalnızca "Neden bahsettiğini bilmiyorum" der.
Bu nedenle, isteme biraz ekleme yapmak istiyoruz.
İsteme araçlar ekleyebiliriz. Her türlü aracı ekleyebiliriz. Bu örnekte gezi bütçesi, seyahat planı vb. araçları ekliyorum. Bunu API'lerimizle Nano modeline iletirseniz model, metin döndürmek yerine artık bir işlev çağrısı döndürebileceğini otomatik olarak anlayacaktır.
Bu örnekte, "Metinle yanıt vermek yerine Mountain View'a taksi için 45 dolarlık bir harcama ekleyeceğim." diyecek.
Bu harika bir şey. Peki Victoria, bu süreç nasıl işliyor?
Bunu nasıl oluştururuz?
Evet.
Peki, kodumuzda araçları nasıl tanımlarız?
Şimdi bunu açıklayacağım.
Android MCP'de bu araçları tanımlamak ve yayınlamak için Jetpack AppFunctions kitaplığını kullanırız.
AppFunctions kitaplığı, uygulamanızın hizmetlerini, verilerini ve işlemlerini Android OS kayıt defterine sunabilir. Böylece, araçları beyan etmek ve açıklamak için aynı işlevleri yeniden kullanabilir ve bunları uygulamanızda kullanabilirsiniz.
Bu sayede kullanıcılar, temsilciler ve sistem düzeyindeki etkileşimler aracılığıyla bu görevi tamamlayabilir.
Şimdi kodda somut bir örnek vereceğim.
/**
* 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...
}Bu, AppFunctionDeclaration örneğidir.
Üstteki KDoc yorumunda, işlevin ne yaptığı ve parametreleri doğal dilde açıklanıyor.
Bu nedenle, modele göndereceğimiz için mümkün olan en net açıklamayı vermek en iyi uygulamadır.
Altında da @AppFunctionAccessLevel notunu görebilirsiniz.
Bu, işlevin görünürlüğünü belirler.
Bu örnekte ACCESS_LEVEL_SELF var. Bu, yazdığımız bu işlevlerin yalnızca uygulamanız ve uygulamanızın temsilcileri tarafından görülebileceği anlamına gelir.
Ardından, askıya alma işlevini tanımlayıp uygulayabilir ve mümkün olduğunca çok uygulama işlevi yazıp hepsini tek bir hizmet sınıfında gruplandırabiliriz.
Anladım.
Bu sayede araçlara sahibiz ve bu araçları yazabiliyoruz. Bu harika bir şey.
Ancak elbette bir temsilci oluşturmak için bu AppFunctions'dan biraz daha fazlasına ihtiyacımız var.
Temsilci yazarken bilmeniz gereken üç bölüm daha var.
Öncelikle temsilciyi tanımlıyoruz.
Sistem talimatlarını, güvenlik önlemlerini, yapılandırmayı ve nasıl çalışılacağını tanımlıyoruz.
Ayrıca AgentRunner da var.
AgentRunner, temsilciyi bizim için çalıştıran araçtır.
Bu, bahsettiğimiz döngü mekanizmasını hallediyor.
Son olarak, AgentEvents var. AgentEvents, sistemin ve API'nin bize ne yaptığını bildirmesini sağlayan bir yöntemdir.
Bu sayede, metin döndürdüğünde, araç çağrısı yaptığında vb. durumlarda bize bilgi verir. Böylece, bu bilgileri kullanıcı arayüzümüzde gösterebiliriz.
Anladım.
Şimdi bu koda bakalım.
Anladım.
Kodu yazmaya başlamadan önce cihazınızda işlev çağrısı özelliğinin kullanılıp kullanılamadığını kontrol etmemiz gerekir. İşlev çağrısı yalnızca Gemini Nano 4'te desteklenir.
val model = Generation.getClient()
val features = model.getAvailableFeatures()
if(FUNCTION_CALLING in features) {
// Device is supported!
} else {
// Graceful fallback
}Öncelikle, üretken modeli getAvailableFeatures çağırarak kontrol etmemiz gerekir.
Özellikler arasında işlev çağrısının listelenip listelenmediğini kontrol edebiliriz.
Aksi takdirde, sorunsuz bir yedekleme uygulayabiliriz.
Anladım.
Şimdi koda geçelim.
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 }
}Cihazımızda işlev çağrısının desteklendiğini biliyoruz. Ardından, Agent.builder kullanarak temsilciyi oluşturmaya başlayabiliriz.
Ardından adını belirleyebiliriz.
Bağlamı ve karakteri tanımlamak için setInstructions.
Son olarak enableAppFunctions yöntemini çağırıyoruz.
Bu işlev çağrısı, daha önce listelediğimiz ve temsilciyi oluşturduğumuz tüm AppFunction'ları otomatik olarak keşfedecek.
Ardından AgentRunner'i ilk kullanıma hazırlamamız gerekiyor.
Bu temsilci çalıştırıcı, yürütme döngüsünü bizim için yönetecek.
Bunu, bir eğitmen, constra-- constructor çağırıp temsilciyi ona ileterek yapıyoruz.
Son olarak, istemi oluşturmak için metne dönüştürülen sesli komutu bir içerik bloğuna sarmalayabiliriz.
Ardından çalıştırıcıyı çağırıyoruz.
İstemde bu işlevi runSingleTurnAsync olarak adlandırıyoruz. Bu işlev, temsilciyi başlatır, çıkarımı çalıştırır ve tüm yapay zeka çıkarımını yapar.
Modelin yalnızca nihai metin yanıtını kullanıcı arayüzümüze aktarmak istiyorsak textTokens yöntemini çağırırız. Bu yöntem, yanıtı geldiği gibi toplar.
Hepsi bu kadar.
Gerçekten de sadece birkaç satır koddan oluşuyor. Bu konuda şaka yapmıyorduk.
Yani sadece birkaç satır kodla bir temsilciniz olur.
Temsilci, istediğiniz işlemi yapar.
Bize metin yanıtı verecek.
Ancak gösterdiğimiz bu ilk demoda yalnızca çok basit bir komut veriyorduk, değil mi?
Örneğin, bir komut verdim ve bir işlem yaptı, hepsi bu. Yani henüz tam teşekküllü bir temsilci değil, değil mi?
Şimdi biraz daha heyecanlı bir şeyler yapalım.
Bir sonraki bölümde, bir komut vermek istiyorum ancak bunu temsilci için biraz daha zorlaştırmak istiyorum.
Şimdi demo cihazıma geri dönelim ve biraz daha zorlaştırıp zorlaştıramayacağımıza bakalım.
Biz zaten oradayız.
Harika.
Dediğim gibi, yarın için bir müze ziyareti planladık.
Müze ziyaretine tıkladığımda giriş ücretinin 25 ABD doları olduğunu görebilirsiniz.
Şimdi, temsilcimizin bunu kendi başına çözüp çözemeyeceğini görelim.
Harcama ekranıma geri dönüp "Müze biletimi harcamalarıma ekle" komutunu veriyorum.
İşte başlıyoruz.
Gördüğünüz gibi seyahat planımı kontrol ediyor, müze ziyaretini buluyor ve harcamalarıma ekliyor.
Burada hedefine ulaşmak için üç adım atıyor ve bana nihai bir metin yanıtı da veriyor.
Tüm bunlar otomatik olarak gerçekleşir.
Bu modelin cihazdaki gerçek hızını gördünüz.
Şimdi slaytlara dönüp bunun nasıl çalıştığına bakalım.
Gördüğünüz gibi, ilk demomuza kıyasla bazı değişiklikler yaptık.
Bu durumda, gördüğünüz gibi modele müze veya masraf hakkında bilgi vermedim. Bu nedenle, istemi ve araçları alan model önce ne yapacağına karar vermeye çalışıyor.
Burada, mevcut seyahat planım hakkında daha fazla bilgi edinmesi gerektiğine karar veriyor ve getItinerary aracını çağırıyor.
Yanıtı aldıktan sonra, seyahat planında bir müze ziyareti olduğunu ve kullanıcının muhtemelen bu ziyaretten bahsettiğini anlar.
Bu etkinliğin ayrıntılarını bulalım.
Bu belirli etkinliğin diğer tüm meta verileri arasında 25 ABD doları olduğunu görür.
Bunu anlar, geri döner ve gider ekleme işlevini çağırması gerektiğini anlar.
Burada, birden fazla adımın art arda gerçekleştiğini görüyoruz.
Victoria, bu konuyu biraz daha ayrıntılı anlatabilir misin?
Evet.
Jolanda'nın az önce anlattığı, gösterdiği demonun somut bir örneğiydi. Ben ise bunu biraz daha kavramsal, soyut bir düzeye taşıyacağım.
Bu durum, çok adımlı işlev çağrısı yaptığımız her seferde gerçekleşir.
Öncelikle bu bizim uygulamamız ve bu da Gemini Nano modeli.
Kullanıcı, sorguyu ve kullanılabilir araçların listesini modele göndererek başlar.
Model, ne göndereceğine karar vermek için bu bilgileri kullanır.
İlk durumda, bir araç çağrısı gönderdiğini varsayalım. Buna "Araç Çağrısı 1" diyelim.
Ardından uygulama, bu araç çağrısını yürütür ve bir araç yanıtı alır.
Ardından, modele geri gönderilecek olan şey aslında orijinal sorguyu, araç listesini, ilk araç çağrısını ve ilk araç yanıtını içeren tam sohbet geçmişidir.
Ardından model, bir sonraki adımda ne yapacağına karar vermek için bu bilgileri kullanır.
Bu nedenle, nihai sonuca ulaşana kadar bu süreç devam edebilir.
Örneğin, model n. araç çağrısını döndürür ve ardından uygulama, tüm görüşme geçmişini tekrar döndürür.
Model, sorgunun yerine getirildiğini anladığında uygulamaya son bir yanıt gönderir. Uygulama da kullanıcı arayüzünde işlemin tamamlandığını gösterir.
Anladım.
Şimdi bunu kodda nasıl uygulayacağımızı açıklayacağım.
/**
* 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 { ... }Tek adımlı kod örneğini hatırlıyorsunuz. Bu ise çok adımlı kod. Birbirlerine çok benziyorlar. En önemli nokta, KDoc'u gerçekten çok net bir şekilde yazmak.
Bu örnekte, getItinerary işlevi yalnızca etkinliklerin üst düzey bir özetini döndürdüğünü açıkça belirtiyor.
Daha ayrıntılı bir gezi bilgisi almak istiyorsak getEventDetails yöntemini bir eventId ile çağırmamız gerektiğini de belirtmiştik.
Ardından, getEventDetails da KDoc yorumuna sahip. Bu yorum, bilet fiyatları ve diğer notlar gibi ayrıntıları alabileceğini söylüyor.
Yorumu kullanarak ve açıkça açıklayarak modeli, araçların birbirleriyle nasıl etkileşim kurduğunu anlayacak şekilde yönlendirebiliriz.
Anladım.
Temsilciyi tüm bu döngüyü, bu aracı döngüsünü gerçekleştirecek şekilde oluşturuyoruz.
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
}
}Tek adımlı örneğe çok benzer, yalnızca iki ekleme yapılmıştır.
İlk olarak, maksimum sıra sayısını ayarlamamız gerekiyor. Bu örnekte beş olarak ayarladık.
Peki, ne işe yarar?
Böylece modelin sonsuz bir döngüye girmesi önlenir.
Örneğin, belirsiz bir istem varsa veya sürekli olarak bir araçtan diğerine geçiş yapıyorsa bu parametreyi ayarlayarak döngüyü durdurabiliriz.
Ardından, çalıştırmak için aynı işlevi çağırırız: runSingleTurnAsync.
Yani tek turlu olarak adlandırılsa da aslında bir tur, bir kullanıcı sorgusu anlamına gelir.
Son kullanıcının beklentisi, bir sorgu gönderdiğinde bir yanıt almaktır. Arka planda neler olduğu, kaç kez döngüye girildiği gibi ayrıntılarla ilgilenmez.
Bu nedenle aynı işlevi kullanmaya devam edebiliriz.
Ayrıca, çok adımlı araç çağrısı biraz daha fazla zaman aldığı için demoda, şu anda hangi işlevi çağırdığını, şu anda neyin yürütüldüğünü ve tamamlanıp tamamlanmadığını gösterdiğini de gördünüz.
Bu nedenle, kullanıcı arayüzünde canlı ilerleme durumunu göstermek istiyoruz.
Yani yalnızca metin yanıtını toplamak yerine, AgentEvent bir FunctionCall veya FunctionResponse yayınladığında araç adını (event.name) gösterebilir ve işlem tamamlandığında TextMessage değerini daha önce olduğu gibi döndürebiliriz.
Olağanüstü!
Bu asenkron çalıştırma yöntemine "tek turlu asenkron çalıştırma" diyoruz. Bu da başka bir API türü daha olduğunu gösteriyor, değil mi?
Evet.
Bu nedenle, tek bir adım uygulamak yeterli değildir.
Çok adımlı bir işlem yapmak istiyoruz.
Şimdi çok turlu oturumlara geçelim. Demoma dönüp biraz daha zorlaştırabiliriz.
Müze ziyareti ve Golden Gate Bay Cruise turu yapacağımızdan bahsediyorduk.
Bu nedenle, rezervasyon yapmadan önce bütçemin yeterli olup olmadığını öğrenmek istiyorum.
Şimdi bir soru soralım.
Bakalım.
Yarın ekibimi Golden Gate Bay Cruise'a götürmeyi düşünüyorum. Fiyatı 180 dolar civarında diye hatırlıyorum.
Bunu yaparsam kalan bütçem ne kadar olur?
Peki ne yapıyor?
Kalan bütçemin ne olacağını yanıtlıyor.
Böylece mevcut seyahat planını anlayabilir.
Bu bilgiye sahip olduğu için bunu söyleyebilir.
Ancak burada ek bir soru da sorabilirim.
Tamam, bu iyi.
Bunu harcamalarıma ekleyebilir misin?
İşte bu kadar.
Para birimi gerekiyor. Demo etkisi.
Tamam, para birimi dolar.
180 ABD doları.
İşte başlıyoruz.
Bu ilk kez oluyor, çok güzel.
Peki, ne yapıyor?
Üç tur sürdüğünü ve masraflara eklendiğini söylüyor. Ayrıca bu oturumu yenilememe de olanak tanıyor.
Üstte küçük bir yenileme düğmesi var. Bu düğmeye bastığımda tüm geçmişi temizleyip baştan başlayabiliyorum.
Böylece slaytlarımıza geri dönebiliriz.
Çalışan demolarımız var, hepsi istediğimiz şeyi yapıyor. Bu yüzden koda geri dönebileceğimizi düşünüyorum.
Evet.
Bu gerçekten çok güzel bir özellik.
Jolanda'nın, Golden Gate Bay Cruise bileti için olduğunu belirtmediğini, yalnızca dolar cinsinden olduğunu söylediğini fark etmişsinizdir.
Peki model tüm bağlamı nasıl biliyor?
Bu, önceki iki kod snippet'inde gösterdiğimden biraz farklı.
// 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()Bu nedenle, AgentRunner ve runSingleTurnAsync kullanmak yerine bir oturum oluşturmamız gerekebilir.
Burada tripId ile runner.getOrCreateSession işlevini çağırıyoruz.
Peki neden bir oturum oluşturmamız gerekiyor?
runSingleTurnAsync, hafızayı sıfırlayacağı için bağlamı olmayacaktır.
Çok turlu bir sohbet gerçekleştiremeyiz.
Çok turlu bir görüşme yapmak istediğinizde temsilci oturumunu kullanırız.
Ardından tripId'i iletiriz.
Bunu neden yapmamız gerekiyor?
Daha önce gördüğünüz Jetpacker uygulaması birden fazla seyahati yönetiyor.
ADS gezisi, Paris gezisi, belki Tokyo gezisi var.
Bu nedenle, bağlamı gezi içinde tutmak istiyoruz.
Birbirimizin işini bozmak istemeyiz, değil mi?
Bu nedenle, runSingleTurnAsync yöntemini çağırmak yerine runAsync yöntemini kullanırız.
Bu farklı bir durum.
Bu, oturumda çalıştırılır ve aynı istemi kullanmaya devam ederiz.
Ardından AgentEvent, FunctionCall, FunctionResponse ve TextMessage'yi daha önce olduğu gibi yayınlayabiliriz.
Jolanda yeni görüşme düğmesini, yani yenile düğmesini tıkladığında session.delete.
Evet.
Anladım.
Tüm bunları yeni temsilci API'leriyle yaptık. Bu, muhteşem bir şey ve birçok kullanım alanınızda harika sonuçlar verecek.
Ancak elbette her zaman hızlı şeyler yapmak veya kolay şeyleri mümkün kılmak istiyoruz. Bununla birlikte, daha karmaşık kullanım alanlarını da sizin için mümkün kılmak istiyoruz.
Bu nedenle, gerekirse bu özelliklerin daha alt düzey bir sürümünü oluşturmak için doğrudan erişim API'lerini de kullanabileceğinizden emin olmak istiyoruz.
Victoria, bunun ne anlama geldiğini ve ne zaman tekrar bu doğrudan erişim düzeyine düşeceğimizi açıklayabilir misin?
Evet, tabii ki Jolanda.
Bu doğrudan erişim, Prompt API'ye aşina olabilirsiniz.
Bu nedenle, burada işlev çağrısını da destekledik ve daha kolay olan temsilci akışı yerine bu API'yi kullanmayı tercih ettiğimiz iki kullanım alanımız var.
İlk örnek, çok basit bir kullanım alanıdır.
Temsilci tabanlı akışta tüm sohbeti, sorguyu, araçları, araç çağrısını, araç yanıtını, her şeyi modele geri gönderdiğimizi hatırlarsınız.
Bazı kullanım alanlarında bunu yapmak istemeyebilir, yalnızca istemi göndermek isteyebiliriz.
Diyelim ki modele bir istem gönderdik ve model bir araç çağrısı döndürdü. Ancak biz bu araç çağrısını döndürmek istemiyoruz, sadece modele başka bir istem göndermek istiyoruz. Bu durumda geçmişi düzenleyebiliriz. Geçmiş üzerinde tam kontrole sahibiz.
Bu konuda daha somut bir örnek vereyim.
// 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()
)Diyelim ki cihazınızın arka plan temasını değiştirebilen bir uygulamanız var.
Kullanıcının "Arka planı gün batımı olarak değiştir" sorgusunu gönderdiğini varsayalım.
Ardından uygulamamız bu işlevi yürüttü ve arka plan gün batımı olarak değiştirildi.
Sonra kullanıcı fikrini değiştirip "Hayır, plaj demek istemiştim." dedi.
Bu durumda, cihazın arka planı gün batımı olarak değiştirdiğini ve şimdi de plaj olarak değiştirmek istediğini belirten tam durumu göndermemiz gerekmez.
Hayır, bunu yapmamız gerekmez.
İlk istemde arka plan bilgileri yer aldığı için yalnızca ilk ve ikinci istemi göndermemiz yeterlidir.
Böylece arka planı plaj olarak değiştirmek istediğinizi anlar.
Bunun için Prompt API'yi kullanabiliriz.
Geçmiş üzerinde tam kontrole sahibiz. Bağlam parçasını cihaz üzerinde kaydediyoruz. Cihaz üzerinde daha sınırlı bir parça penceresi olduğu için bu sayede daha fazla parça kaydedebiliyoruz.
İkinci kullanım alanı ise bunun tam tersi.
Ajan tabanlı API'nin henüz destekleyemediği karmaşık kullanım alanlarımız varsa, örneğin karmaşık görevleri çözmek için birbirleriyle etkileşime giren birkaç alt ajanın bulunduğu çoklu ajan sistemlerimiz veya hem cihaz üzerinde modellerin hem de bulut modellerinin bulunduğu karma bir sistemimiz varsa ya da özel bellek ve bağlam sıkıştırması istiyorsak ne olur?
Bu, daha önce bahsettiğim geçmişin tam kontrolü gibi geçmişle ilgilidir.
Bağlam penceresi sınırlı olduğu için her şeyi modele geri göndermek istemediğimizi, yalnızca en önemli kısmı veya belki de en son kısmı özetlemek istediğimizi varsayalım. Bu konuda tam kontrole sahip olduğumuz için doğrudan erişim API'sini kullanırız.
Bu kullanım alanları için Agent Development Kit (ADK) veya Crew gibi çerçeveleri kullanabilir ya da kendi özel düzenleme çerçevenizi oluşturabilirsiniz.
Anladım.
Temsilci API'si hakkında anlatacaklarımız bu kadar.
Ancak değinmek istediğimiz bir konu daha var.
Sürekli uygulama içi temsilcilerden bahsediyoruz. Yani kendi uygulamanızda kendi temsilci deneyiminizi oluşturuyorsunuz. Ancak AppFunctions'ı nasıl kullandığımızdan da bahsettik. Yalnızca kendi uygulamanızda işlem yapıyorsanız neden AppFunctions'ı kullanmalısınız?
Sizin gibi geliştiricilerin aynı şeyi iki kez yapmaktan hoşlanmadığını biliyoruz.
Kod kopyalayıp yapıştırmayı ve iş mantığını kopyalamayı sevmiyoruz.
AppFunctions sayesinde, araçlarımızı yalnızca uygulama içi temsilcimizin kullanmasına izin vermek yerine çok basit bir değişiklikle sistem temsilcilerinin de kullanımına sunabiliriz.
Peki, bu durum kodda nasıl görünür?
Evet.
AppFunctions'ı bir kez yazmamız yeterli. Ardından, istediğimiz katmanlara sunabiliriz.
Daha önce ACCESS_LEVEL_SELF kullandığımızı hatırlarsınız. Bu, yazdığımız işlevleri yalnızca uygulama içi temsilcimize gösteriyordu ancak bu erişim düzeyini ACCESS_LEVEL_ANDROID_TRUSTED olarak da değiştirebiliriz.
Bu sayede işlevler, Android OS'teki güvenilir aracılara gösterilir.
Bu nedenle işlevleri, komutları veya veritabanı mantığını yeniden yazmanız gerekmez.
AppFunctions, uygulamanızın hem dahili hem de harici sesi olarak işlev görür.
Bunun da demosunu görmek isteriz, değil mi?
Şimdi telefona dönelim ve son demomuz için Jetpacker uygulamasından çıkalım.
Buradan çıkıp Gemini'a gidebiliriz.
Gemini'da @Jetpacker'ı çağırabiliriz.
Bu özelliğin şu anda yalnızca sınırlı beta sürümünde kullanılabildiğini belirtmek isteriz. Bu nedenle, özellik şu anda herkes tarafından kullanılamayacak ancak test edilip denenebilecek.
Jetpacker'a bakalım.
Ne yapmalıyız?
Bu sabah kahve içmiştim, değil mi?
ADS seyahat masraflarına 7 dolarlık kahveyi ekleyebilir misin?
İşte başlıyoruz.
Jetpacker uygulamasına bağlandığını görebilirsiniz.
İşte bu kadar.
Harcamayı benim için ekledi.
Jetpacker'daki her şeyi nasıl kullanabileceğimizi gördük.
Jetpacker'ın en güzel yanı, uçak modunu da açabilmemiz.
Bugün bunu göstermedik ama uçak modunu açabiliriz. Demolarda gördüğünüz her şeyi gösterebiliriz. Bunu göstermek istiyorum çünkü bana güvenmenizi istiyorum. Wi-Fi'ı kapatalım.
Ekleyebileceğim iyi bir demo örneği veya harcama örneği olan var mı?
Bakalım.
Belki "What is it?" (Nedir?) şarkısını yapabiliriz.
Araba kiralama iyi olurdu, değil mi?
Anladım.
"Araç kiralama için 300 TL'lik bir harcama ekle" diyelim.
İşte bu kadar.
Anladım.
Harika.
Tamam, slaytlara geri dönelim.
AppFunctions'tan bahsediyorduk.
Bu konuya çok kısaca değindiğimizi biliyorum.
Daha fazla bilgi edinmek isterseniz Dan ve Sarah'nın bu özelliği nasıl etkinleştirebileceğiniz, nasıl test edebileceğiniz, hangi araçlara sahip olduğumuz ve daha fazlası hakkında ayrıntılı bilgi verdiği başka bir konuşma da var.
Bugün birçok farklı konuyu ele aldık.
Tek adımlı, çok adımlı ve çok turlu işlevleri inceledik. Son olarak, sistem AppFunction'ı nasıl kullanabileceğinizi gördük.
Şimdi, ayrılmadan önce birkaç ipucu paylaşalım.
Evet.
Bu nedenle, temsilci deneyimleri oluşturmaya başlarken bu üç ipucunu unutmayın.
İlk olarak, araç setinin odağını koruyun.
Bu nedenle, cihaz üzerinde çalışan modellerin daha küçük ölçekli olması nedeniyle sınırlı kapasiteye sahip olduğunu unutmayın.
Bu nedenle, onlarca araç kaydetmek yerine daha az sayıda araç kullanmak daha iyidir.
Daha önce de söylediğim gibi, modelin ne döndüreceğine karar vermek için kullandığı şey bu olduğundan net KDoc açıklamaları yazın.
Ardından maksimum sıra sayısı için bir güvenlik önlemi de belirlemeliyiz.
Bu sayede, modelin kafası karıştığında sonsuz döngüye girmesi de önlenir.
Bu durum, kullanıcının pilini tüketebileceğimiz anlamına gelir.
Bu sayede kullanıcının pilini koruyabiliriz.
Son olarak, işlev çağrısının cihazda desteklenip desteklenmediğini kontrol etmemiz ve düzgün bir yedekleme uygulamamız gerekir.
Örneğin, desteklenmiyorsa bulut modellerine veya standart kullanıcı arayüzünüze başvurabilirsiniz.
Tamam, bence bu konuyu kaynaklarımızla kapatabiliriz.
QR kodlarını bir kez daha göstereceğim.
Lütfen göz atın.
Bu konuda Android Skills'den de yararlanabileceğinizi unutmayın.
Bu sayede her bir ayrıntıyı derinlemesine incelemeniz gerekmez.
Bu yeni temsilci API'siyle birlikte kullanabileceğiniz kapsamlı belgeler ve bir Android becerisi de mevcut.
Bu nedenle, bu konuya göz atın.
Tüm bunları test etmek için AICore Geliştirici Önizlemesi'ni indirin. İyi kodlamalar.
/**
* 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()
)