Gemini Nano ile temsilci tabanlı iş akışları oluşturma

ML Kit'in cihaz üzerinde çalışan temsilci tabanlı iş akışları (com.google.mlkit:genai-agent, AICore Geliştirici Önizlemesi programı aracılığıyla kullanılabilir) sayesinde Gemini Nano ve Android AICore sistem hizmeti tarafından desteklenen otonom, çok turlu etkileşimli temsilciler oluşturabilirsiniz.

Doğrudan model erişimi sağlayan GenerativeModel sınıfı üzerine inşa edilen AgentRunner sınıfı, tüm aracı döngülerini otomatikleştirir: çok adımlı işlemleri planlama, yazılı araçları çağırma (ör. uygulama deposunu sorgulama, arka uç API'sini çağırma veya yerel veritabanını okuma), araç sonuçlarını değerlendirme ve sohbet bağlamını sıfır ağ gecikmesi ve tam kullanıcı gizliliğiyle cihaz üzerinde yerel olarak yönetme. GenerativeModel sınıfı, List<Content> nesnesi (USER ve MODEL rolleriyle) ve sessionId değeri kullanarak istemci tarafından yönetilen çok turlu geçmişi destekler. AgentRunner sınıfı ise AgentSession arayüzünü kullanarak yönetilen çok turlu oturumlar sağlar. Bu oturumlar, bellek içi sohbet geçmişini turlar arasında otomatik olarak kaydeder. Böylece transkriptleri manuel olarak yönetmeniz gerekmez. Statik araç bildirimleri ve sistem talimatları da cihaz üzerinde otomatik olarak önbelleğe alınır. Böylece ek yapılandırma olmadan ilk jeton süresi gecikmesi azaltılır.

Temel bileşenler

  • Agent sınıfı: Agent.builder() yöntemini kullanarak talimatları, kullanılabilir araçları ve yürütme önlemlerini tanımlar. Otomatik olarak maxTurns = 10 değerine ayarlanır.
  • AgentRunner sınıfı: GenerativeModel sınıfının üzerine inşa edilmiştir. Otonom akıl yürütme döngülerini ve yönetilen oturumları cihaz üzerinde modellerle yönetir. Dispatchers.IO üzerinde varsayılan olarak ana iş parçacığı için güvenlidir. temperature, varsayılan olarak null değerine ayarlanır (bu değer, modelin varsayılan değerine göre belirlenir; açgözlü kod çözme için temperature = 0.0f değerini iletin).
  • AgentSession arayüzü: runAsync(content) ve close() yöntemleriyle, bellek içi görüşme geçmişini otomatik olarak kaydeden runner.getOrCreateSession(sessionId) yöntemi kullanılarak elde edilen kapsamlı çok aşamalı görüşme oturumu işleyicisi.
  • AgentEvent arayüzü: Metin jetonları ve araç güncellemeleri yayınlayan reaktif Kotlin Flow akışı.
  • AgentTool sınıfı: Android AppFunctions (@AppFunctionDeclaration) kullanılarak tanımlanan, model tarafından çağrılabilen tür atanmış işlevler.
  • GenAiException sınıfı: MAX_TURNS_EXCEEDED, NOT_AVAILABLE veya BUSY gibi hata durumlarını bildiren yazılı istisna hiyerarşisi (com.google.mlkit.genai.common.GenAiException).

Ön koşullar ve bağımlılıklar

Agent API'yi entegre etmeden önce geliştirme ortamınızın ve Gradle yapılandırmanızın aşağıdaki şartları karşıladığından emin olun.

Platform ve donanım gereksinimleri

Gradle yapılandırması

Modülünüzün build.gradle.kts dosyasına genai-agent bağımlılığını ekleyin:

dependencies {
    // Gemini Nano agentic workflows
    implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")

    // Optional: Only required when defining tools using Android AppFunctions
    implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
    ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}

genai-agent için ek açıklama işleyicisi veya derleyici eklentisi gerekmez ancak androidx.appfunctions ile araç tanımlamak için appfunctions-compiler KSP eklentisi gerekir.

Hızlı başlangıç: Temsilci başlatma

Aşağıdaki kod örneğinde, cihaz üzerinde çalışan bir temsilcinin ve çalıştırıcının nasıl örneklendirileceği gösterilmektedir:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner

// Configure the agent with system instructions
val agent = Agent.builder()
    .setInstructions("You are a helpful on-device assistant.")
    .build()

// Create the runner to execute prompts
val runner = AgentRunner(agent)

Araçları tanımlama

Model, uygulama verilerini sorgulamak, arka uç hizmetleriyle iletişim kurmak veya uygulama içi işlemleri tetiklemek için yazılı araçları çağırabilir. Araçlar, Android AppFunctions (androidx.appfunctions) kullanılarak bildirimli olarak tanımlanır:

package com.example.ai.agent.tools

import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@AppFunctionSerializable
data class OrderDetails(
    val orderId: String,
    val status: String,
    val eta: String,
)

@AppFunctionServiceEntryPoint(
    serviceName = "OrderAppFunctionService",
    appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {

    /**
     * Fetches details and delivery status for an in-app order.
     *
     * @param orderId The alphanumeric order identifier
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun fetch_order_details(
        orderId: String
    ): OrderDetails = withContext(Dispatchers.IO) {
        // Query local database or call your backend API
        OrderDetails(
            orderId = orderId,
            status = "Out for delivery",
            eta = "2:30 PM",
        )
    }
}

Oluşturulan hizmeti uygulamanızın AndroidManifest.xml dosyasına kaydedin:

<service
    android:name=".OrderAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="order_app_functions.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>

ACCESS_LEVEL_SELF sabitiyle arayan erişimini kısıtlama

AppFunctions'ın sisteme veya harici arayanlara açık olması gerekmez. Android'de erişim denetimi, arayan kimliklerini incelemek yerine arayan erişim düzeyleri aracılığıyla gerçekleştirilir.

Varsayılan olarak (ACCESS_LEVEL_ANDROID_TRUSTED sabiti kullanılarak) AppFunctions, EXECUTE_APP_FUNCTIONS sistem iznine sahip platform sertifikalı cihaz üzerinde asistanlar tarafından çağrılabilir. Aracınız yalnızca uygulamanızın dahili temsilcisi için tasarlanmışsa (ör. kullanıcı sepetlerini veya özel veritabanı kayıtlarını yönetme) ACCESS_LEVEL_SELF sabitini kullanarak arayan erişimini kısıtlayın:

import androidx.appfunctions.AppFunctionAccessLevel
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.ExperimentalAppFunctionsApi
import androidx.appfunctions.metadata.AppFunctionMetadata

@AppFunctionServiceEntryPoint(
    serviceName = "OrderAppFunctionService",
    appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {

    /**
     * Cancels an order. Restricted strictly to this app's internal agent.
     */
    @OptIn(ExperimentalAppFunctionsApi::class)
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
        level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
        isCompatEnforcementEnabled = true,
    )
    fun cancel_order(
        orderId: String
    ): String {
        // Only your app's internal agent can invoke this function
        return "Order $orderId cancelled successfully."
    }
}

ACCESS_LEVEL_SELF sabitiyle kısıtlandığında platform, harici platformların ve sistem asistanlarının işlevi IPC üzerinden keşfetmesini veya yürütmesini engeller.

Temsilciyi yapılandırma

Sistem talimatlarıyla bir Agent örneği yapılandırın ve AppFunctions'ı etkinleştirin, ardından AgentRunner örneğini ilk kullanıma hazırlayın:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner

// Configure the agent with system instructions and enable AppFunctions
val agent = Agent.builder()
    .setName("OrderAssistant")
    .setInstructions(
        "You are a helpful assistant for looking up store orders. " +
        "Use available tools to fetch order information."
    )
    .enableAppFunctions(true) // Automatically discovers all registered AppFunctions in your app
    .build()

// Initialize the runner
val runner = AgentRunner(agent)

İsteğe bağlı olarak, tüm uygulama içi işlevleri göstermek yerine keşfi yalnızca belirli AppFunction kimlikleriyle sınırlamak için addAppFunctionIds("fetch_order_details", "cancel_order") yöntemini çağırın.

Temsilci varsayılanları

  • Sıra sınırları: Agent.Builder sınıfı, sonsuz araç çağırma döngülerini önlemek için otomatik olarak maxTurns = 10 değerini sonlandırma koşulu olarak varsayılan olarak ayarlar. Standart sohbet veya araç çağırma iş akışları için setMaxTurns(10) yöntemini manuel olarak çağırmanız gerekmez.
  • Deterministik açgözlü kod çözme: AgentRunner sınıfı, varsayılan temperature parametresini null olarak ayarlar ve temel modelin varsayılan değerini kullanır. Cihaz üzerinde deterministik araç seçimi ve JSON şeması uyumluluğu için AgentRunner oluşturucusuna temperature = 0.0f (temperature) değerini iletin. Mobil modellerde halüsinasyon riskini ve şema bozulmasını artıran bulut tarzı sıcaklıklar (> 0,4f) ayarlamaktan kaçının.

Çok turlu yürütme ve akış etkinlikleri

Android ViewModel sınıfındaki runner.getOrCreateSession(sessionId) yöntemini kullanarak bir AgentSession örneği alın. Oturum, anlık metin jetonları ve araç yürütme güncellemeleri yayınlayan bir Flow<AgentEvent> akışı döndürür:

package com.example.ai.agent

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.genai.agent.AgentEvent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentSession
import com.google.mlkit.genai.common.GenAiException
import com.google.mlkit.genai.prompt.Content
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

data class AgentUiState(
    val messages: List<String> = emptyList(),
    val currentResponse: String = "",
    val activeToolStatus: String? = null,
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

class ChatViewModel(
    private val runner: AgentRunner,
    private val sessionId: String = "chat_session"
) : ViewModel() {

    // Scoped session preserves conversation history across configuration changes (screen rotations)
    private val session: AgentSession = runner.getOrCreateSession(sessionId)

    private val _uiState = MutableStateFlow(AgentUiState())
    val uiState: StateFlow<AgentUiState> = _uiState.asStateFlow()

    fun sendMessage(userText: String) {
        _uiState.update {
            it.copy(
                messages = it.messages + userText,
                currentResponse = "",
                activeToolStatus = null,
                isLoading = true,
                errorMessage = null
            )
        }

        viewModelScope.launch {
            val content = Content.builder().text(userText).build()
            session.runAsync(content)
                .catch { e ->
                    val message = when {
                        e is GenAiException && e.errorCode == GenAiException.ErrorCode.MAX_TURNS_EXCEEDED ->
                            "Agent exceeded the maximum reasoning turns allowed."
                        e is GenAiException && e.errorCode == GenAiException.ErrorCode.NOT_AVAILABLE ->
                            "On-device AI is currently unavailable."
                        e is GenAiException && e.errorCode == GenAiException.ErrorCode.BUSY ->
                            "On-device AI is busy. Please try again."
                        else -> e.localizedMessage ?: "Agent error"
                    }
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            activeToolStatus = null,
                            errorMessage = message
                        )
                    }
                }
                .collect { event ->
                    when (event) {
                        is AgentEvent.TextMessage -> {
                            _uiState.update {
                                it.copy(
                                    currentResponse = if (event.isPartial) {
                                        it.currentResponse + event.text
                                    } else {
                                        event.text.ifEmpty { it.currentResponse }
                                    },
                                    activeToolStatus = null
                                )
                            }
                        }
                        is AgentEvent.FunctionCall -> {
                            _uiState.update {
                                it.copy(activeToolStatus = "Executing ${event.name}...")
                            }
                        }
                        is AgentEvent.FunctionResponse -> {
                            _uiState.update {
                                it.copy(activeToolStatus = "Received data from ${event.name}")
                            }
                        }
                        else -> {}
                    }
                }

            // Finalize message once stream completes
            _uiState.update {
                it.copy(
                    messages = it.messages + it.currentResponse,
                    currentResponse = "",
                    activeToolStatus = null,
                    isLoading = false
                )
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        // Cleanly evicts conversation memory from RAM when ViewModel is destroyed
        session.close()
    }
}

Metin jetonlarını doğrudan aktarma

Araç yürütme ayrıntılarının kullanıcı arayüzünde ayrı olarak gösterilmesi gerekmeyen etkileşimli arayüzler için Flow<AgentEvent> arayüzündeki textTokens() uzantı işlevini kullanın:

import com.google.mlkit.genai.agent.textTokens
import com.google.mlkit.genai.prompt.Content

val content = Content.builder().text("What is my order status?").build()
session.runAsync(content)
    .textTokens()
    .collect { token ->
        print(token)
    }

Tek adımlı yürütme

Diyalog geçmişinin RAM'de saklanmaması gereken tek seferlik görevler (ör. fotoğraf etiketleme veya geçici metin özetleme) için doğrudan runner.runSingleTurnAsync(content) yöntemini çağırın:

// Ephemeral single-turn: cleans up session state from RAM automatically upon completion
val content = Content.builder()
    .text("Summarize the order history for customer #1042: ...")
    .build()

runner.runSingleTurnAsync(content)
    .textTokens()
    .collect { token ->
        displaySummary(token)
    }

runner.runSingleTurnAsync(content) yöntemini doğrudan çağırırken AgentRunner örneği, dahili olarak geçici bir oturum kimliği tahsis eder ve tamamlandığında görüşme durumunu bellekten otomatik olarak silerek bellek sızıntılarını önler.

Oturum yaşam döngüsü ve bellek yönetimi

Çok turlu diyalog geçmişi, bellekte saklanır ve sessionId değeriyle dizine eklenir:

  • Yapılandırma değişiklikleri: AgentSession örneğini bir Android ViewModel sınıfında tutmak, her dönüşte dize eklemeye gerek kalmadan ekran döndürme işlemlerinde sohbet geçmişini otomatik olarak korur.
  • Oturum çıkarma: Bir görüşme dizisi kapatıldığında oturumu eşzamanlı olarak çıkarmak için session.delete() yöntemini çağırın veya RAM'i boşaltmak için ViewModel.onCleared() yönteminde askıya alınmayan session.close() yöntemini çağırın.
  • Toplu çıkarma: Belirli bir oturumu serbest bırakmak için runner.deleteSession(sessionId) yöntemini, önbelleğe alınmış tüm sohbetleri çıkarmak için runner.clearAllSessions() yöntemini çağırın.
  • Çalıştırıcı yaşam döngüsü: AgentRunner sınıfı, AutoCloseable arayüzünü uygular. Bir çalıştırıcıyı ekranlar arasında paylaşırken her ekranın ViewModel.onCleared() yönteminde bağımsız AgentSession tutamaçlarını kapatın ve çalıştırıcıyı tamamen kapatırken runner.close() yöntemini çağırın.
  • Eşzamanlı oturumlar: Tek bir AgentRunner örneği, aynı anda birden fazla farklı oturumu barındırabilir. Her sessionId değeri, ayrı bir görüşme geçmişi tutar.

GenAiException sınıfıyla hata işleme

Temsilci yürütme sırasında oluşan çalışma zamanı hataları (ör. temsilci yanıt oluşturmadan maksimum araç sırasına ulaştığında veya cihaz üzerinde yapay zeka geçici olarak kullanılamadığında) GenAiException istisnası (com.google.mlkit.genai.common.GenAiException) olarak yayınlanır veya atılır. Kotlin Coroutines Flow catch operatörünü kullanarak hataları reaktif bir şekilde işleyin:

session.runAsync(content)
    .catch { e ->
        if (e is GenAiException) {
            when (e.errorCode) {
                GenAiException.ErrorCode.MAX_TURNS_EXCEEDED -> {
                    // Turn limit exceeded
                }
                GenAiException.ErrorCode.NOT_AVAILABLE -> {
                    // Gemini Nano or AICore unavailable
                }
                GenAiException.ErrorCode.BUSY -> {
                    // System busy or rate limit reached
                }
                else -> {
                    // Other GenAiException errors
                }
            }
        }
    }
    .collect { event ->
        // Process response events
    }

Anahtar hata kodları

  • GenAiException.ErrorCode.MAX_TURNS_EXCEEDED (-112): Temsilci, nihai bir yanıt oluşturmadan maksimum sıra sınırını (maxTurns sınırı) aştı.
  • GenAiException.ErrorCode.NOT_AVAILABLE (8): Gemini Nano modeli veya AICore sistem hizmeti kullanılamıyor ya da hazır değil.
  • GenAiException.ErrorCode.BUSY (9): Sistem meşgul veya arama uygulaması kotasını aştı.