Google'da Ürün Müdürü
Android, Gemini gibi temsilcilerin uygulamalar genelinde görevleri yönettiği bir zeka sistemine dönüşüyor. Uygulamanızın yeni kullanıcı deneyimlerini etkinleştirmesi ve bu temsilci tabanlı iş akışlarına katılabilmesi için temel özelliklerini sunması gerekir.
Bu oturumda, uygulamanızı Android'in akıllı katmanıyla entegre etme konusunda pratik bilgiler verilecek. Cihaz üzerinde Android MCP, sunucu tarafı MCP ve görev otomasyonu gibi temel entegrasyon yollarını inceleyecek ve kullanım alanınız için doğru yolu seçmenize yardımcı olacak bir karar çerçevesi sunacağız. Ayrıca Kişiye Özel ile proaktif deneyimleri de keşfedeceğiz.
Şunları öğreneceksiniz:
AppFunctionDeclaration ile tanımlayın, KDoc açıklamaları ekleyin ve Kotlin'in varsayılan değerlerini kullanmaktan kaçının.Transkript
İşte böyle.
Herkese merhaba.
Ben Geliştirici İlişkileri'nden Dan Galpin.
Ben de Sarah Shamsie. Android'in geliştirici zekası çerçeveleri üzerinde çalışan bir ürün müdürüyüm.
Bugünkü oturumumuza katıldığınız için çok mutluyuz.
Kullanıcıların cihazlarıyla etkileşim kurma biçiminde bir paradigma değişimine tanık oluyoruz.
Web çağında kullanıcılar, yolculuklarına genellikle web tarayıcısına gidip arama işlevini kullanarak başlardı.
Mobil çağda ise kullanıcı davranışının tek seferlik işlemleri tamamlamak için daha özel, yerel uygulamalar kullanmaya yöneldiğini gördük.
Örneğin,
Arkadaşımdan akşam yemeği daveti geliyor.
Bu davete yanıt vermek için takvimimi kontrol edip müsait olup olmadığıma bakmam, etkinliği takvime eklemem, etkinliğe gitmek için araç çağırmam ve arkadaşıma katılabileceğimi bildirmem gerekiyor.
Eskiden bunun için uygulama atlama denen bir işlem yapmam gerekiyordu. Yani işlemleri tamamlamak ve yolculuğumu gerçekleştirmek için cihazımdaki farklı uygulamalar arasında geçiş yapmam gerekiyordu.
Ancak artık zeka çağındayız.
Yapay Zeka Çağı'nda kullanıcılar, yolculuklarına yapılması gereken belirli işlemleri değil, üst düzey niyetlerini ve hedeflerini ifade ederek başlıyor.
Kullanıcıların cihazlarını yalnızca işlem yapmayı sağlayan bir araç olarak değil, ihtiyaçlarını proaktif olarak tahmin eden ve hedeflerine ulaşmalarına yardımcı olan bir araç olarak görmeye başladığı temel bir değişim yaşanıyor.
Kullanıcıların bu akıllı sistemin yalnızca mobil cihazlarında değil, saat, araba, TV, dizüstü bilgisayar, XR ve gözlük gibi çeşitli form faktörlerinden oluşan zengin bir ekosistemde çalışmasını beklediğini fark ettik.
Yapay Zeka Çağı'nda, uygulamalar arası tek bir istem, daha önce bahsettiğimiz akşam yemeği daveti örneğini kolaylaştırabilir.
Cihazımdaki bir temsilci, tek bir istemle takvimimi kontrol etme, etkinlik ekleme, araç rezervasyonu yapma ve mesaj gönderme gibi ihtiyacım olan tüm işlemleri tek seferde yapmama yardımcı olabilir.
Bugün, Android'in bu alandaki planları ve geliştiriciler olarak birinci sınıf deneyimler oluşturmak için neler yapabileceğiniz hakkında daha fazla bilgi paylaşacağız.
Dan.
Teşekkürler, Sarah.
Android, temelinde zekayı geliştirmeye devam ediyor ve bu da uygulamalarınızın bu Zeka Çağı'na hazırlanması için yeni yollar sunuyor.
Bu iki geliştirici zekası temelinden ilki, uygulama içi zekadır. Bu, uygulamalarınızı daha akıllı hale getirmek ve uygulamanıza zeka katmakla ilgilidir. Mesaj dizilerini özetlemek, yanıtları yeniden yazmak ve resimleri anlamak için ML Kiti aracılığıyla Gemini Nano 4 gibi cihaz üzerinde çalışan modelleri kullanabilir veya uygulamanızın işlevlerini çağırmak için doğal dili kullanabilirsiniz.
Hatta Firebase AI Logic aracılığıyla buluttaki Gemini modellerini doğrudan uygulamanızda kullanabilirsiniz.
Ancak bugün ikinci temel unsura odaklanacağız: Uygulamanızın özelliklerinin akıllı sistemde kullanılabilir olmasını sağlama.
Peki, zeka sistemi nedir?
Yani Sarah'nın daha önce bahsettiği istemli deneyimler hakkında konuşmanın ve bunları barındırmanın yanı sıra, uygulama ekosistemindeki deneyimleri iyileştirmek için derin entegrasyonlar sağlayan bir sistemdir.
Örneğin, Gemini Intelligence için tasarlanmış cihazlar, öneriler, özetlenmiş bildirimler, iyileştirilmiş ses girişi ve formların otomatik olarak doldurulması gibi özellikler sunmak için bu entegrasyon noktalarından yararlanır.
Öneriler artık proaktif yardım adlı bir özelliğin parçasıdır.
Şimdiye kadar, istemli yardımdan bahsettik.
Kullanıcının bir temsilciye kendisi adına bir veya daha fazla görevi yerine getirmesini söylediği bu duruma hepimiz alışkınız.
Ancak proaktif yardım, aslında kullanıcının yapabileceği bir sonraki işlemi veya işlemleri tahmin etmekle ilgilidir.
Akşam yemeği örneğimize dönersek temsilci, mesaja yanıt olarak kullanıcıya proaktif bir şekilde öneriler sunabilir. Bu öneriler, daveti takvimine eklemek veya bir araç paylaşımı rezervasyonu yapmak olabilir.
Peki, kullanıcıdan nasıl bilgi ister?
Proaktif yardım; kilit ekranı, bildirimler, sanal klavyeler ve hatta seçili uygulamalar gibi yerlerde işlemleri gösterebilir.
Peki, uygulamanızın bu proaktif çiplerde görünmesini sağlayarak trafiği uygulamanıza nasıl yönlendirebilirsiniz?
En yeni SDK sürümünü ve en modern araçlarımızı kullanarak uygulama geliştirme gibi işlemleri zaten yapıyorsunuz. Ancak şu anda yapabileceğiniz en iyi şey, uygulamanızın istemli yardıma da hazır olduğundan emin olmaktır. Sarah bu konudan birazdan bahsedecek.
Teşekkürler Davut.
İstemli yardımda kullanıcılar doğrudan bir temsilciye giderek görevleri başlatmasını ve yönetmesini ister. Bu görevler farklı uygulamalar arasında da olabilir.
Bu sayede, daha önce manuel olarak yapılan ve zaman alan iş akışları otomatikleştirilebilir.
Android'deki akıllı sistemimiz için öncelikle kullanıcının cihazındaki uygulamalardan yararlanabilen Gemini gibi sistem temsilcilerine odaklanıyoruz.
Uygulamanız bu deneyimde birkaç farklı şekilde görünebilir ve sistem temsilcisiyle etkileşim kurabilir.
Platform tabanlı Görev Otomasyonu çözümümüz ve sistem temsilcisine doğrudan API entegrasyonları mevcuttur.
Mümkün olduğunda her zaman doğrudan API entegrasyonlarıyla derleme yapmanızı öneririz.
Bunlar geliştirici tarafından kontrol edilir. Bu sayede, uygulamanızın ve markanızın nasıl görüneceğine karar verebilir ve bunu kontrol edebilirsiniz.
Ayrıca daha hızlı ve daha güvenilir olduklarından kullanıcılarınıza daha kaliteli bir deneyim sunarlar.
Temsilciyle entegrasyon için kullanabileceğiniz iki API yolu vardır.
Model Context Protocol (MCP), sunucu tarafı işlev çağrısı için sektördeki en iyi uygulamadır.
Ancak cihaz üzerinde araç çağırma için Android'deki birleştirilmiş işlev çağrısı çerçevesi olan Android MCP'yi resmi olarak duyuruyoruz.
Android MCP, Jetpack AppFunctions kitaplığımız tarafından desteklenir. Bu kitaplık, hem teknik hem de kavramsal olarak MCP ile yakından uyumludur ve birçok Android geliştiricisinin aşina olduğu tanıdık ve deyimsel bir Kotlin uygulamasını destekler.
Bu sayede API'nin uygulanması nispeten sezgisel ve kolay olur. Ayrıca, mevcut bir sunucu tarafı MCP entegrasyonundan edindiğiniz bilgileri kullanabilirsiniz.
Birçok MCP temel öğesi, Jetpack AppFunctions yapısıyla bire bir eşleşir.
Bunu açıklamak için birkaç örnek üzerinden gidelim.
MCP aracı uç noktası, AppFunctionDeclaration ile ek açıklama eklenmiş bir Kotlin işleviyle iyi bir şekilde eşleşir.
Pydantic modelleriniz, AppFunctionSerializable ile ek açıklama eklenmiş Kotlin veri sınıflarıyla eşlenir.
Ancak Android MCP, akış yapılabilir HTTP ve/veya standart G/Ç aktarımı yerine optimize edilmiş Android Binder IPC'de çalışır.
Bugün, Jetpack kitaplığı AppFunctions'ın beta sürümünün kullanıma sunulduğunu duyurmaktan büyük heyecan duyuyoruz.
Şimdi, bir API yolunu ne zaman kullanmanın mantıklı olduğunu ve özellikle Android MCP'yi ne zaman kullanmanın mantıklı olduğunu merak ettiğinizden eminim.
Genel bir ilke olarak Android MCP, özellikle bir kullanıcı yolculuğunu tamamlamak için cihaz üzerinde veya Local Context'ten yararlanmak istediğinizde kullanışlıdır.
Android MCP'nin gerçekten öne çıktığı bazı örneklere göz atalım.
Özellikle yerel, şifrelenmiş verilere erişmeniz gerektiğinde Android MCP'yi kullanabilirsiniz.
Bu durum, özellikle uçtan uca şifreleme nedeniyle mesaj geçmişinin cihaz üzerinde kalması gereken mesajlaşma ve iletişim uygulamaları için önemlidir.
Android MCP, temsilcilerin desteklediği tamamen cihaz üzerinde ve çevrimdışı işlemler için de kullanışlıdır.
Wear OS iş ortağımız Samsung Health, tamamen cihaz üzerinde yönetilen bir koşu ve egzersiz başlatma işlevi kullanıma sundu.
Kullanıcılar genellikle bu tür işlemlerin çok hızlı, yüksek güvenilirlikle, düşük gecikmeyle ve bazen de düşük bağlantılı ortamlarda yapılmasını ister. Tüm araç çağırma, yönlendirme ve sorgu yönlendirme işlemlerinin cihaz üzerinde yapılabilmesi bunu sağlar.
Cihaz sensörlerine erişmek veya cihaz donanımıyla doğrudan etkileşimde bulunmak istiyorsanız Android MCP de tercih edilen API yoludur.
Örneğin, birçok otomotiv uygulaması, kullanıcıların UWB bağlantısı üzerinden ve genellikle ellerini kullanmadan araç kapılarını anında açmalarına olanak tanımak ister.
Android MCP bu konuda yardımcı olabilir.
Son olarak, uygulamalarınız veya API'niz için kullanılabilir bir Cloud Endpoints yoksa bu çözümden yararlanabilirsiniz.
Cihaz üreticimizin birinci taraf uygulamalarının çoğu yalnızca istemci tarafında çalışır. Bu nedenle Android MCP'yi kullanırlar.
Örneğin, Gemini yalnızca istemci tarafı olan galeri uygulamalarındaki (ör. Xiaomi Galeri) fotoğrafları aramak ve almak için kullanılabilir.
Burada Gemini'ı açıp "Geçen hafta Xiaomi Galeri'de çektiğim Android bot fotoğraflarını bul" diyebilirim.
Xiaomi, fotoğraf aramak için bir uygulama işlevi sunuyor. Gemini bu işlevi çağırıyor ve ilgili fotoğraflar doğrudan Gemini uygulamasının kullanıcı arayüzünde gösteriliyor. Böylece kullanıcının uygulamadan ayrılması gerekmiyor.
Android OS'teki cihaz üzerinde işlemler için de aynı mimariyi kullanıyoruz.
Android MCP, kısa süre önce kullanıma sunulan ve Android OS'in Gemini uygulamasına doğrudan entegrasyonu olan Cihaz Yardımı özelliğini destekler.
Bu özellik Pixel ve Lenovo cihazlarda kullanıma sunuldu ve şu anda daha geniş Android OEM ekosisteminde ölçeği genişletiliyor.
Kullanıcılar, Gemini'a gidip ayarlarını, cihaz ayarlarını ve sistem ayarlarını nasıl optimize edebileceklerini sorarak cihaz deneyimlerini iyileştirmek için Cihaz Yardımı'nı kullanabilirler.
Örneğin, renk körü bir kullanıcı çeşitli menülerde arama yapmak yerine Gemini'a "Renk körüyüm.
Bu telefonu kendim için nasıl daha kullanışlı hale getirebilirim?"
Gemini, yerel uygulama işlevlerini çağırıp çalıştırır, isteği akıllıca değerlendirir ve kullanıcının sistem ayarlarını değiştirebileceği gerçek yöntemler ve deneyimi iyileştirmek için cihaz ayarlarını değiştirebileceği belirli yöntemler önerir.
Ardından, uygulama içi denetimler doğrudan Gemini uygulamasının kullanıcı arayüzünde gösterilir. Böylece kullanıcılar hemen kontrolü ele alabilir.
Örneğin, renk körlüğü optimizasyonu isteğini yerine getirirken Gemini, renk düzeltme açma/kapatma düğmesini gösterdi. Böylece kullanıcılar değişikliği anında yapabilir.
Android MCP deneyimleri belirli cihazlarda zaten kullanılabilir. Android ekosistemine daha fazla kullanım alanı eklemek için birden fazla iş ortağıyla çalışıyoruz.
Açık Android MCP aracı beyanları altın standardımız olsa da kullanıcıların temsilcilerden karmaşık veya daha az yaygın kullanım alanlarını tamamlamalarını isteyeceği durumlar olacağını biliyoruz. Platform destekli Görev Otomasyonu özelliğimiz bu noktada devreye girer.
Bu modelde, OEM tarafından önceden yüklenen temsilci, Android'in bilgisayar kontrolü API'lerini kullanarak uygulama yüzeyindeki dokunma etkinliklerini simüle edebilir.
Bu işlem için geliştiricilerin kod değişikliği yapması gerekmez. Temsilcinin, uygulama ekranını analiz ederek kullanıcı gibi kullanıcı arayüzüne dokunması gerekir.
Kullanıcı kontrolünü ve güvenliğini sağlamak için temsilcilerin, ödeme akışları gibi hassas olabilecek işlemlerde duraklaması ve devam etmek için kullanıcı onayı istemesi çok önemlidir.
Şimdi bir örnek inceleyelim.
Burada, bir kullanıcı kahve ve çikolata turunun fotoğrafını çekmiş ve Expedia'ya sormak istiyor veya Gemini'dan Expedia'ya bakıp benzer bir tur bulup bulamayacağını öğrenmesini istiyor ancak katılımcı sayısını altı kişi olarak ayarlıyor.
Gemini, doğrudan API entegrasyonuna gerek kalmadan Expedia'nın kullanıcı arayüzünde arka planda bir kullanıcının yapacağı şekilde gezinir.
Ardından, bulduğu alakalı seçenekleri gösterir ve son onay ile rezervasyon zamanı geldiğinde kullanıcıya sorunsuz bir şekilde aktarır.
Bu üç farklı yolun birbirini dışlamadığını ve aslında birlikte daha iyi sonuç verdiğini anlamak önemlidir.
Kullanıcılar ve geliştiriciler, temsilcilerin sunucu uygulamaları genelinde araçları koordine edebilmesini ve ardından yerel Android uygulamasının kullanıcılara en kapsamlı ve bütünsel deneyimi sunmasını beklemelidir.
İşte böyle.
Teşekkürler, Sarah.
Android MCP'yi biraz daha ayrıntılı inceleyeceğiz.
Mümkün olduğunda MCP kod snippet'lerini, Antigravity'nin yardımıyla oluşturulan seyahat örnek uygulamamız Jetpacker'dan aldığımı bilmenizi isterim.
Bunu açılış konuşmasında görmüşsünüzdür.
ML Kit üretken yapay zeka ve ML Kit konuşma tanıma API'leriyle cihaz üzerinde entegrasyonlar da dahil olmak üzere her türlü akıllı özellikten yararlanabilirsiniz.
Artık Android MCP ve bulut tabanlı MCP benzer bir yapıya sahiptir.
Her ikisinde de istemciler ve sunucular vardır.
MCP dünyasında sunucular araçlar sağlar, istemciler ise bu araçları keşfedip çağırabilir.
Android MCP ile artık uygulamanız sunucu, temsilci ise istemci olur.
Android platformu, araç kayıt defteri rolünü üstlenir.
Uygulamalar, sunucuyu sağlamak için bir hizmet kaydederek başlar. Biz de buradaki örneğimizde AppFunctions Jetpack kitaplığı bağımlılıklarını ekleyerek başlayacağız.
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)Ardından, AppFunctionService oluşturmak için KSP ek açıklamalarını kullanacağız.
Bu hizmet sayesinde uygulama işlevleriniz Android yaşam döngülerine uygun şekilde çalışabilir.
AppFunctions'ın kullanabileceği veri depolarını eklemek için Hilt ile bağımlılık ekleme özelliğini kullanabiliriz.
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
...Özür dilerim.
Diyagramımıza geri dönelim.
Uygulama, Android MCP sunucumuz için hizmeti ayarladıktan sonra araçları AppFunctions biçiminde ve kullandıkları ilişkili veri modelleriyle birlikte kaydeder.
Artık AppFunctionSerializable ek açıklamasını kullanarak bir veri modeli kaydedebilirsiniz. Bu KDoc da oldukça kullanışlı.
/** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
/** The event's unique identifier. */
val id: String,
/** The trip ID this event belongs to. */
val tripId: String,
/** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
val type: String,
/** The timestamp of the event. */
val timestamp: Long,
/** The title of the event. */
val title: String,
/** The location of the event. */
val location: String,
/** A description of the event. */
val description: String?,
/** The title of the trip this event belongs to. */
val tripTitle: String? = null,
/** Optional title of a matched itinerary event when added as a reminder. */
val matchedEventTitle: String? = null,
/** Execution status of the tool call (e.g., SUCCESS). */
val status: String? = "SUCCESS",
/** Human-readable completion message instructing the agent not to repeat the call. */
val message: String? = null,
)Bu bilgiler, temsilcinin modeli nasıl kullanacağını anlamak için kullandığı meta veriler haline gelir.
İşlevinizi araç olarak kaydetmek için AppFunctionDeclaration ek açıklamasını kullanabilirsiniz.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
fallback
}
events.map { it.toSerializable(tripTitle = trip?.title) }
}
}isDescribedByKDoc true olarak ayarlandığında KDoc, temsilciye işlevinizin ne yaptığını anlaması için bir açıklama sağlar ve insanların da bunu anlamasına yardımcı olur.
/**
* Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
* trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
* specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
* visitor tips.
*
* @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
...Parametre tasarımıyla ilgili en iyi uygulamalardan bazıları şunlardır:
/**
* @param tripId Optional ID of the trip (e.g., '2026-1').
* Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
// resolveTrip defaults to the active trip if tripId == null
val trip = resolveTrip(tripId = tripId)Öncelikle, net ve farklı adlara sahip minimum parametre alt kümesine odaklanmanız gerekir.
Temel olmayan parametreleri null değer alabilir hale getirin ve işlev gövdesinde varsayılan değerlerini çözün.
Bu sadece en iyi uygulama değildir.
AppFunctions kitaplığı, atlanan null değer alabilen parametreler için null değerini ilettiğinden Kotlin varsayılan değerleri yoksayılır. Bu nedenle, bu yöntem işe yaramaz.
İlk örnekle ilgili bir şey göstermeyeceğim bile ancak tümünü yakala parametrelerini kullanmayın.
Tam bağlam veya görüşme geçmişi gibi şeyler kötü.
Bu tür istemler, temsilcilerin büyük miktarda veri aktarmasına ve veri sızıntısına yol açabilir. Bu nedenle, bu tür istemlerin kullanılması önerilmez.
AppFunction varsayılan olarak kullanıcı arayüzü iş parçacığında yürütüldüğünden, uygun eş yordam dağıtıcısını seçerek uzun süreli işlemlerin arka planda gerçekleştiğinden emin olmanız gerekir.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
...Temsilcinin bir hatayı nasıl çözeceğini anlayabilmesi için gerektiğinde ayrıntılı bilgiler içeren AppFunction istisnaları oluşturmak istiyorsunuz.
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
message: Message
): Result {
...
throw AppFunctionException(
errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
errorMessage = "To address has invalid syntax!"
)
}Artık bir AppFunction'ımız var. Şimdi mimari diyagramımıza geri dönelim.
Temsilci istemci uygulaması, AppFunctions'ı çağırmak için platforma kaydolur.
Örneğin, Gemini bu tür bir araçtır.
Bunun için ayrıcalıklı bir izne sahip olmaları gerekir. Kayıtlı aracılar daha sonra Android platformundan AppFunction meta verilerini arayabilir ve kullanıcının isteğini yerine getirmek için kayıtlı bir veya daha fazla AppFunction'ı yürütmeleri gerekip gerekmediğine karar verebilir.
Bir veya daha fazla AppFunction'dan bahsetmişken, temsilci aslında nasıl karar verir?
Yapmanız gereken son bir şey var: Uygulamanızın özelliklerini bir uygulama meta veri dosyasında açıklamak.
<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
appfn:description="This app manages trips and travel expenses.
Operational Patterns:
- Use 'getAllTrips' to find the correct 'tripId' before
adding expenses or managing trip details.
Constraints:
- Expenses must be associated with a valid trip ID."
appfn:displayDescription="@string/user_visible_description" />Temsilciler, araçları seçmek için önce AppFunctionAppMetadata içindeki bu açıklamayı okur, ardından her bir AppFunction'ın açıklamalarını okur.
Bu nedenle, açıklamanızın başında uygulamanızın veya araçlarınızın birincil alanını kısaca açıklamanız gerekir.
Ardından, temsilcinin işlemlerin mantıksal akışını anlamasına yardımcı olmak için operasyonel kalıpları ekleyebilirsiniz. Bu, ön koşulları belirlemek veya temsilciyi görev tamamlama için en uygun yollara yönlendirmek açısından faydalıdır.
Ayrıca, giriş verisi türlerinin mantıksal sınırlarını tanımlayan veya operasyonel kalıpları güçlendiren kısıtlamalarla ilgili bir bölüm ekleyebilirsiniz.
Android MCP'nin temel özellikleri bu şekilde. Ancak yapabileceğiniz birkaç şey daha var.
Yapabileceğiniz güzel şeylerden biri, AppFunctions'ı dinamik olarak etkinleştirip devre dışı bırakmaktır.
viewModelScope.launch {
val newState =
if (enabled) {
AppFunctionManager.APP_FUNCTION_STATE_ENABLED
} else {
AppFunctionManager.APP_FUNCTION_STATE_DISABLED
}
appFunctionManager?.setAppFunctionEnabled(id, newState)
}Önemli: Bu, AppFunction'ın yalnızca kullanıcı giriş yaptığında kullanılabilir olmasını istediğiniz durumlarda önemlidir.
Uygulamanızdaki belirli bir ekran veya etkinlikle sınırlı AppFunctions da oluşturabilirsiniz.
@AppFunctionSignature(
scope = AppFunctionMetadata.SCOPE_ACTIVITY,
appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
/** Adds the item currently shown on screen to the cart. Returns the new cart size. */
suspend fun addToCart(quantity: Int): Int
}Bu nedenle, AppFunction yerine AppFunctionSignature tanımlarsınız.
Ardından, kullanıcı arayüzünüzde AppFunction için bir işleyici uygulayabilirsiniz.
val addToCartAppFunctionAdapter =
appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
coroutineScope.launch {
appFunctionManager.handleAppFunction(
addToCartAppFunctionAdapter.adapt { quantity ->
val newCartSize = cart.addItem(quantity)
newCartSize
}
)
}Bu özelliğin en iyi yanı, işleyicilerin yaşam döngüsünün, kullandığınız eş yordam kapsamının yaşam döngüsüne otomatik olarak bağlanmasıdır.
Şimdi bu AppFunctions istek bağdaştırıcısını önbelleğe almanız gerekir. Kitaplık, örneklendirmek için arka planda yansıtma kullandığından AppFunctions istek bağdaştırıcısını işlemeniz gerekir.
Ayrıca, kaydı doğrudan Android yaşam döngüsüne bağlı olmayan kapsamlı AppFunctions oluşturma işlevi de vardır.
Son olarak, erişim düzeyi adında bir kavramımız var.
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
...Ayrıca, erişim düzeyini "self", "system" veya "trusted" olarak ayarlayabilirsiniz. Bu sayede, hangi işlevlerin yalnızca kendi uygulama içi temsilciniz (ör. ML Kit GenAI ile oluşturulmuş bir temsilci) tarafından kullanılabileceğini, hangi işlevlerin yalnızca sistem tarafından kullanılabileceğini ve hangi işlevlerin güvenilir sistem temsilcileri tarafından kullanılabileceğini belirtebilirsiniz.
Kendi uygulama içi temsilcinizi oluşturmanıza yardımcı olmak için ML Kiti'nin, birleşik araç çağırma çerçevesinin bir parçası olarak AppFunctions'ı yerel olarak desteklediğini duyurmuştuk.
Bunları bir kez tanımlayıp hem sistem düzeyindeki temsilciler hem de yerel uygulama içi modellerde kapsam elde edersiniz. Bu sayede, parçalı araç bildirimleri gibi sorunlardan kaçınabilirsiniz.
Daha fazla bilgi edinmek için "ML Kiti GenAI ile uygulamanızda ajan tabanlı yetenekleri kullanma" başlıklı konuşmayı izleyebilir veya Jetpack'teki en yeni özellikleri inceleyebilirsiniz.
Oldukça etkileyici.
Şimdi Sarah'ya dönelim.
İşte böyle.
Dan, Android MCP'nin arka planda nasıl çalıştığını gösterdi.
Şimdi bunu uygulamaya geçirelim.
Uygulamanızın bir sunucu, temsilcilerin ise istemci olduğunu unutmayın. Bu nedenle, uygulamanızın temsilciler için iyi bir sunucu olmasını sağlamayı hedeflemelisiniz.
Seyahatle ilgili örnek uygulamamız Jetpacker'ı, ilk araç bildiriminden Gemini üzerinden gerçek bir işlev çağrısına kadar takip edeceğiz.
AppFunctions'ınızı manuel olarak yazabilir veya başlamanıza yardımcı olması için bir kodlama temsilcisi kullanabilirsiniz.
Android KSA aracılığıyla Android Studio'ya bağlayabileceğiniz veya istediğiniz kodlama aracısıyla kullanabileceğiniz işlevlerinizi yazmaya başlamanıza yardımcı olabilecek AppFunctions becerimizi kullanıma sunduk.
android skills add appfunctionsBeceri üç adımda çalışır.
İlk olarak, kod tabanınızı analiz ederek medya oynatma, not oluşturma veya mesaj gönderme gibi faydalı araçlar oluşturabilecek temel kullanıcı yolculuklarını belirler.
Ardından, bu yolculukları ihtiyaç duydukları uygulama ve platform desteğiyle birlikte sunan AppFunction bildirimleri oluşturur.
Örneğin, Gradle ve KSP, uygulama meta veri XML'i ve bağımlılık ekleme.
Son olarak, KDoc açıklamalarınızı hassaslaştırır. Temsilci, işlevinizi ne zaman ve nasıl çağıracağına karar vermek için tam olarak bu açıklamaları kullanır.
Aradığımız sonuç türü budur.
Bu, Jetpacker için addExpense işlevidir.
/**
* Adds an expense to a trip.
*
* @param tripId ID of the trip. If null, the active trip is used.
* @param category Expense category. If null, it's inferred from the title.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
title: String,
amount: Double,
currency: String,
tripId: String? = null,
category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
if (amount <= 0.0) {
throw AppFunctionInvalidArgumentException("amount must be greater than zero")
}
val trip = resolveTrip(tripId)
?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
val expense = Expense(trip.id, title, amount, currency,
category = category ?: inferExpenseCategory(title))
expenseDao.insertExpense(expense)
expense.toSerializable(remainingBudgetUsd(trip.id))
}Açıklama, fiil ve kaynakla başlayan tek satırlık bir cümledir: Geziye harcama ekler.
Kullanıcılar her zaman belirtmeyebileceğinden gezi ve kategori alanları null değer alabilir ancak işlev, bunları gövdede çözer.
İstek başarısız olduğunda ise AppFunctionException döndürür. Böylece temsilci, sorunun ne olduğunu anlayabilir.
Şimdi de test aşamasına bakalım.
İşlevleriniz artık temsilcilerin kullanabileceği hizmetlerdir.
Bu nedenle, test etmeniz gereken iki bölüm vardır: işlevinizin davranışı ve temsilcinin davranışı.
İşleviniz için muhtemelen daha önce yazdığınız birim testiyle başlayacağız. Ancak işlev çağrısını temsilci yaptığında dikkat edilmesi gereken birkaç nokta vardır.
İlk olarak, Kotlin varsayılan değerleri burada geçerli değildir.
Atlanan bir parametre null, false veya 0 olarak gelir.
Dolayısıyla, daha önce varsayılan olarak 10 olan bir sınır burada 0 olur.
Çağrı da ana iş parçacığında başlar ve işlevinizi bir testten çağırmak bunu her zaman netleştirmeyebilir. Bu nedenle, standart G/Ç'nizi bir arka plan dağıtıcısında çalıştırmanız gerekir.
Bir işlem başarısız olduğunda AppFunctionException oluşturun.
Diğer tüm hatalar, ham istisna mesajınız eklenmiş şekilde genel bir bilinmeyen hata olarak temsilcinize ulaşır.
Girişleriniz bir modelden geleceği için boşluklar, USD gibi dolar eş anlamlıları ve aralık dışı değerler gibi uç durum senaryolarını mutlaka test etmelisiniz.
Temsilci tarafında ise temsilcinin aracınızı doğru zamanda seçip seçmediğini ve işlevi doğru şekilde yürütüp yürütmediğini bilmeniz gerekir.
Her temsilci biraz farklı davranır. Bu nedenle, işlevlerinizi kullanmasını beklediğiniz her temsilciyle test etmeniz önemlidir.
Test etmek için açık kaynak örnek temsilcimizi veya Gemini'ı kullanabilirsiniz.
Gemini'ın AppFunctions ile nasıl etkileşim kurduğunu görmek veya Gemini uygulamasında Android MCP'yi başlatmak istiyorsanız bugün erken saatlerde kullanıma sunduğumuz ve şu anda önizleme aşamasında olan Bağlı Uygulamalar Platformu'nu kullanabilirsiniz.
Uygulamanızı Bağlı Uygulamalar Platformu aracılığıyla Gemini ve Arama'ya genişletme konulu önceki konuşmamıza göz atarak daha fazla bilgi edinebilirsiniz.
Önizleme programına katıldıktan sonra Gemini ile test etme süreci iki adımdan oluşur.
Öncelikle Google Connected Console'da Gemini'ın kullanabileceği bir bağlı uygulama oluşturacağız.
İkinci olarak, Google Play Console'da uygulama paketinizi yüklemeniz gerekir. Böylece AppFunctions'a erişebilir ve dahili test kanalında başlayabilirsiniz. Bu sayede test kullanıcıları işlevleri doğrulayabilir.
Google Connected Console'dan başlayalım.
Burada, sağ üstteki "Create an app" (Uygulama oluştur) düğmesini kullanarak bağlı bir uygulama oluşturacaksınız.
Bağlı uygulamayı oluştururken bu ekranda Android MCP'yi seçin.
Sunucu tarafı MCP uygulaması oluşturuyorsanız sunucu kodunuzu buraya yüklemeniz gerekir.
"Kur"u tıkladığınızda, geliştirici hesabınızla ilişkili tüm Google Play uygulamaları burada gösterilir ve bağlı uygulamanıza bağlanmaya hazır olur.
Henüz bir Google Play uygulamanız yoksa önce Google Play Console'da bir uygulama oluşturmanız gerekir.
Burada Jetpacker'ı zaten eklemiştik. Bu nedenle, onu bağlayacağız.
Ardından, kullanıcıların uygulamanızla etkileşim kurmaya çalıştıklarında Gemini ön ucunda görecekleri ayrıntıları ekleyin.
Uygulama simgesi, görünen ad, bağlı uygulamanızın özellikleri ve kullanıcıların uygulamayla etkileşim kurmak için kullanabileceği örnek istemler bu kapsamdadır.
Kullanıcılara en iyi şekilde rehberlik etmek için bunlar açıklayıcı olmalıdır.
Bağlı uygulama kurulumu tamamlandı. Bağlı uygulamanın işlevlere nasıl eriştiğini merak ediyor olabilirsiniz.
İşlevler uygulama kodunuzda yer aldığından, şimdi Google Play Console'a gidip uygulama paketinizi yüklememiz ve bir test kanalı oluşturmamız gerekiyor. Böylece, gerçek kullanıcılarla test edebiliriz.
Bu ekranda uygulama paketini yükleyeceğiz. Ardından, Google Play'in AppFunctions'ı algılayabildiğini göreceksiniz. Bunları doğrudan bağlı uygulamanıza bağlayabilirsiniz.
Artık Jetpack bağlı uygulamasını, işlevleriniz entegre edilmiş şekilde görebiliriz.
Ardından, işlevlerinizi doğrulayacak ve düzgün çalıştıklarından emin olacak test kullanıcıları eklememiz gerekir.
Bu nedenle, bağlı uygulama sürümlerinizi buradan tıklayın.
Bu ekranda "Test kullanıcılarını yönet"i seçebilir ve Gemini'da kullanıma sunulduğunda bağlı uygulamanıza erişebilecek sınırlı bir test kullanıcısı listesi yapılandırabilirsiniz.
Bu test kanalına, diğer sürümlerde olduğu gibi test kullanıcıları ekleyebilirsiniz.
Bağlı uygulama için Google Play uygulamanızla aynı test kullanıcıları listesine sahip olduğunuzdan emin olun.
Şimdi entegrasyonu test edelim.
Kullanıcının telefonundaki Jetpacker uygulamasında bu zirve için bir gezi kaydedilmiş.
Uygulamayı bağlamak için Gemini'ı açacağım.
Cihazımda, daha önce Google Play'e yüklediğimiz uygulamanın aynı sürümünün yüklü olduğundan emin olmam gerekiyor. Ayrıca Gemini için test kullanıcısı listesine eklenen bir hesabı kullanmamız gerekiyor.
Burada Jetpacker'dan bahsedip Mountain View'daki otel odamın onay numarasını soracağım.
Gemini, cihazda Jetpacker'ın AppFunctions'ını çağırır ve "Redwood Grove Hotel'de RED11223" yanıtını verir.
Ardından, "ADS için ne kadar bütçem kaldı?" diye soracağım.
İşlev çağrısı 1.850 ABD doları döndürür.
Şimdi bir değişiklik yapmak istiyorum. Bu nedenle Gemini'dan ADS gezime havaalanı Uber'i için 45 ABD doları eklemesini isteyeceğim.
Gemini, bunu doğrudan seyahat planıma eklemek için daha önce oluşturduğumuz addExpense işlevini çağırır.
Jetpacker'ı açtığımda harcama zaten orada görünüyor ve bütçe de buna göre azalıyor.
Gemini için bağlı uygulamalar geliştirmeye hemen başlamak istiyorsanız şu anda önizleme aşamasında olan Bağlı Uygulamalar Platformu'na erişim için başvuruda bulunun.
İşte böyle.
Şimdi bu konuyu toparlayalım.
Mobil bilişimde büyük bir değişimin başındayız ve uygulamanızın bu değişime hazır olduğundan emin olmanın tam zamanı.
Bu nedenle, uygulamanızın yüksek değere sahip temel kullanıcı yolculuklarını belirleyerek işe başlayın.
Sunucu tarafı MCP'yi kullanarak bulutunuzun arka ucunu gösterin ve uçtan uca şifrelenmiş veriler, yerel işlemler, cihaz sensörleri ve diğer cihaz üzerindeki öğelerle çalışmak için Android MCP'yi kullanın.
Şimdi prototip oluşturmak için Android Studio ve Android KSA'yı indirebilirsiniz.
AndroidX AppFunctions beta bağımlılığını derleme dosyalarınıza ekleyebilir ve ilk cihaz üzerinde işlevinizi yerel olarak test edebilirsiniz.
Gemini ile üretime erken başlamak için bu QR kodunu kullanarak kaydolabilirsiniz.
Ayrıca, Bağlı Uygulamalar Platformu ile çalışma ve kendi uygulama içi temsilcinizi çalıştırmak için dahili AppFunctions'ı ML Kiti veya kendi temsilcinizle nasıl kullanabileceğinizle ilgili konuşmaları da izleyebilirsiniz.
Zaman ayırdığınız için çok teşekkür ederiz.
Android için geliştirdiğiniz muhteşem deneyimleri görmek için sabırsızlanıyoruz.
implementation(libs.androidx.appfunctions)
ksp(libs.androidx.appfunctions.compiler)@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
.../** Represents an event in a trip itinerary. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TimelineEventSerializable(
/** The event's unique identifier. */
val id: String,
/** The trip ID this event belongs to. */
val tripId: String,
/** The type of event (e.g., TRANSPORTATION, ACCOMMODATION, FOOD_AND_DRINK). */
val type: String,
/** The timestamp of the event. */
val timestamp: Long,
/** The title of the event. */
val title: String,
/** The location of the event. */
val location: String,
/** A description of the event. */
val description: String?,
/** The title of the trip this event belongs to. */
val tripTitle: String? = null,
/** Optional title of a matched itinerary event when added as a reminder. */
val matchedEventTitle: String? = null,
/** Execution status of the tool call (e.g., SUCCESS). */
val status: String? = "SUCCESS",
/** Human-readable completion message instructing the agent not to repeat the call. */
val message: String? = null,
)@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
if (fallback.isNotEmpty()) { eventDao.insertEvents(fallback) }
fallback
}
events.map { it.toSerializable(tripTitle = trip?.title) }
}
}/**
* Retrieves the itinerary events (with their eventId, title, type, and location) for a specific
* trip. Note: getItinerary returns event summaries only; call getEventDetails(eventId) with a
* specific event's ID to retrieve ticket prices, hotel nightly rates, baggage allowance, or
* visitor tips.
*
* @param tripId Optional ID of the trip (e.g., '2026-1'). Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?: tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
.../**
* @param tripId Optional ID of the trip (e.g., '2026-1').
* Defaults to the active trip if omitted.
* @return A list of events in the itinerary.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
// resolveTrip defaults to the active trip if tripId == null
val trip = resolveTrip(tripId = tripId)@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
val trip = resolveTrip(tripId = tripId)
val targetTripId = trip?.id ?: ActiveTripSession.activeTripId ?:
tripId?.takeIf { it.isNotBlank() } ?: "2026-1"
val dbEvents = eventDao.getEventsForTrip(targetTripId).first()
val events =
if (dbEvents.isNotEmpty()) { dbEvents } else {
val fallback = DummyData.events.filter { it.tripId == targetTripId }
...@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun send(
message: Message
): Result {
...
throw AppFunctionException(
errorCode = AppFunctionException.ErrorCode.ERROR_INVALID_ARGUMENT,
errorMessage = "To address has invalid syntax!"
)
}<AppFunctionAppMetadata
xmlns:appfn="http://schemas.android.com/apk/androidx.appfunctions"
appfn:description="This app manages trips and travel expenses.
Operational Patterns:
- Use 'getAllTrips' to find the correct 'tripId' before
adding expenses or managing trip details.
Constraints:
- Expenses must be associated with a valid trip ID."
appfn:displayDescription="@string/user_visible_description" />viewModelScope.launch {
val newState =
if (enabled) {
AppFunctionManager.APP_FUNCTION_STATE_ENABLED
} else {
AppFunctionManager.APP_FUNCTION_STATE_DISABLED
}
appFunctionManager?.setAppFunctionEnabled(id, newState)
}@AppFunctionSignature(
scope = AppFunctionMetadata.SCOPE_ACTIVITY,
appFunctionXmlFileName = "cart_functions"
)
fun interface AddCurrentItemToCart {
/** Adds the item currently shown on screen to the cart. Returns the new cart size. */
suspend fun addToCart(quantity: Int): Int
}val addToCartAppFunctionAdapter =
appFunctionManager.getHandleAppFunctionRequestAdapter<AddCurrentItemToCart>()
coroutineScope.launch {
appFunctionManager.handleAppFunction(
addToCartAppFunctionAdapter.adapt { quantity ->
val newCartSize = cart.addItem(quantity)
newCartSize
}
)
}@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun getItinerary(tripId: String? = null): List<TimelineEventSerializable> {
return withContext(Dispatchers.IO) {
...android skills add appfunctions/**
* Adds an expense to a trip.
*
* @param tripId ID of the trip. If null, the active trip is used.
* @param category Expense category. If null, it's inferred from the title.
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun addExpense(
title: String,
amount: Double,
currency: String,
tripId: String? = null,
category: String? = null,
): ExpenseSerializable = withContext(Dispatchers.IO) {
if (amount <= 0.0) {
throw AppFunctionInvalidArgumentException("amount must be greater than zero")
}
val trip = resolveTrip(tripId)
?: throw AppFunctionElementNotFoundException("No trip with ID $tripId")
val expense = Expense(trip.id, title, amount, currency,
category = category ?: inferExpenseCategory(title))
expenseDao.insertExpense(expense)
expense.toSerializable(remainingBudgetUsd(trip.id))
}