رویدادهای جاری‌سازی و ادغام میانای کاربر

درطول گردش کار عاملی، کلاس AgentRunner و میانای AgentSession جریان‌های رویداد واکنشی را منتشر می‌کنند که نشان‌دهنده فرایند استدلال مدل، فراخوانی‌های ابزار، و پاسخ‌های متنی است. این صفحه نشان می‌دهد که چگونه رویدادها را جمع‌آوری کنید و آن‌ها را بااستفاده از Kotlin Coroutines و Jetpack Compose به میانای کاربر خود متصل کنید.

آشنایی با میانای AgentEvent

روش‌های AgentSession.runAsync() و AgentRunner.runSingleTurnAsync() یک جاری‌سازی Flow<AgentEvent> را برمی‌گردانند. به‌جای اینکه منتظر بمانید تا کل حلقه استدلال تمام شود، برنامه‌تان به‌روزرسانی‌های هم‌زمان را هنگام اجرای عامل دریافت می‌کند:

نوع رویداد

شرح

کنش معمول میانای کاربر

AgentEvent.TextMessage

تکه‌های نوشتاری افزایشی (isPartial = true) و سپس پاسخ کامل نهایی (isPartial = false) تولیدشده توسط Gemini Nano را منتشر می‌کند.

وقتی event.isPartial، true است، event.text را به حبابک گپ فعال اضافه کنید (یا از تابع افزونه textTokens() استفاده کنید).

AgentEvent.FunctionCall

وقتی Gemini Nano ابزاری را انتخاب می‌کند و برای اجرای آن آماده می‌شود، این رویداد صادر می‌شود (event.name و event.args را ارائه می‌دهد).

نشانگر درحال انجام را نمایش دهید (برای مثال، «درحال بررسی وضعیت سفارش...»).

AgentEvent.FunctionResponse

وقتی برنامه اجرای تابع ابزار را به‌پایان می‌رساند، منتشر می‌شود (event.name و event.response را ارائه می‌دهد).

وضعیت ابزار را به تکمیل‌شده تغییر دهید.

جمع‌آوری رویدادها در کلاس ViewModel

از کلاس ViewModel Android برای هماهنگ کردن ارسال پیام‌واره و نمایش وضعیت میانای کاربر به صفحه‌های Compose استفاده کنید:

package com.example.ai.agent.ui

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 = "default_chat_session"
) : ViewModel() {

    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()
        // Release session memory and cancel active coroutines
        session.close()
    }
}

استخراج مستقیم داده‌واحدهای نوشتاری

برای میاناهای مکالمه‌ای که جزئیات اجرای ابزار نیازی به نمایش جداگانه در میانای کاربر ندارند، از تابع افزونه textTokens() در میانای Flow<AgentEvent> استفاده کنید:

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

val content = Content.builder().text(userText).build()
session.runAsync(content)
    .textTokens()
    .collect { token ->
        appendTokenToChatBubble(token)
    }

پرداز زدن در Jetpack Compose

از حالت AgentUiState در یک تابع ترکیب‌پذیر برای ارائه نوشتار جاری‌شده و فعالیت ابزار زنده استفاده کنید:

package com.example.ai.agent.ui

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun AgentChatScreen(
    viewModel: ChatViewModel,
    modifier: Modifier = Modifier
) {
    val state by viewModel.uiState.collectAsState()
    var inputText by remember { mutableStateOf("") }

    Column(modifier = modifier.fillMaxSize().padding(16.dp)) {
        LazyColumn(
            modifier = Modifier.weight(1f).fillMaxWidth(),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(state.messages) { message ->
                Card(modifier = Modifier.fillMaxWidth()) {
                    Text(text = message, modifier = Modifier.padding(12.dp))
                }
            }

            if (state.activeToolStatus != null) {
                item {
                    Text(
                        text = state.activeToolStatus ?: "",
                        style = MaterialTheme.typography.bodySmall,
                        color = MaterialTheme.colorScheme.secondary
                    )
                }
            }

            if (state.currentResponse.isNotEmpty()) {
                item {
                    Card(
                        colors = CardDefaults.cardColors(
                            containerColor =
                                MaterialTheme.colorScheme.surfaceVariant
                        ),
                        modifier = Modifier.fillMaxWidth()
                    ) {
                        Text(
                            text = state.currentResponse,
                            modifier = Modifier.padding(12.dp)
                        )
                    }
                }
            }
        }

        Row(
            modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            TextField(
                value = inputText,
                onValueChange = { inputText = it },
                modifier = Modifier.weight(1f),
                placeholder = { Text("Ask about an order...") }
            )
            Button(
                onClick = {
                    if (inputText.isNotBlank()) {
                        viewModel.sendMessage(inputText)
                        inputText = ""
                    }
                },
                enabled = !state.isLoading
            ) {
                Text("Send")
            }
        }
    }
}

مراحل بعدی