স্ট্রিম ইভেন্ট ও UI ইন্টিগ্রেশন

এজেন্টিক ওয়ার্কফ্লো চলাকালীন, AgentRunner ক্লাস এবং AgentSession ইন্টারফেস মডেলের যুক্তি প্রক্রিয়া, টুল ইনভোকেশন এবং টেক্সচুয়াল উত্তরকে উপস্থাপন করে এমন রিঅ্যাক্টিভ ইভেন্ট স্ট্রিম নির্গত করে। এই পৃষ্ঠায় দেখানো হয়েছে কীভাবে ইভেন্ট সংগ্রহ করতে হয় এবং Kotlin Coroutines ও Jetpack Compose ব্যবহার করে আপনার UI-এর সাথে সেগুলি কানেক্ট করতে হয়।

AgentEvent ইন্টারফেস বোঝা

AgentSession.runAsync() এবং AgentRunner.runSingleTurnAsync() পদ্ধতি একটি Flow<AgentEvent> স্ট্রিম রিটার্ন করে। সম্পূর্ণ রিজনিং লুপ শেষ হওয়ার জন্য অপেক্ষা করার পরিবর্তে, এজেন্ট এক্সিকিউট করার সাথে সাথে আপনার অ্যাপ রিয়েল-টাইম আপডেট পায়:

ইভেন্টের ধরন

বিবরণ

সাধারণ UI অ্যাকশন

AgentEvent.TextMessage

Gemini Nano-র মাধ্যমে জেনারেট করা চূড়ান্ত সম্পূর্ণ উত্তরের (isPartial = false) পরে ইনক্রিমেন্টাল টেক্সট চাঙ্ক (isPartial = true) এমিট করে।

event.isPartial, true হলে (অথবা textTokens() এক্সটেনশন ফাংশন ব্যবহার করুন) অ্যাক্টিভ চ্যাট বাবলে event.text যোগ করুন।

AgentEvent.FunctionCall

Gemini Nano কোনও টুল বেছে নিয়ে সেটি এক্সিকিউট করার জন্য প্রস্তুত হলে এটি নির্গত হয় (event.name এবং event.args প্রদান করে)।

প্রসেস চলছে এমন ইন্ডিকেটর দেখান (যেমন, "অর্ডারের স্ট্যাটাস চেক করা হচ্ছে...")।

AgentEvent.FunctionResponse

অ্যাপ টুল ফাংশন এক্সিকিউট করা শেষ করলে নির্গত হয় (event.name এবং event.response প্রদান করে)।

টুলের স্ট্যাটাস 'সম্পূর্ণ হয়েছে' হিসেবে পরিবর্তন করুন।

ViewModel ক্লাসে ইভেন্ট সংগ্রহ করা

প্রম্পট ডিসপ্যাচ কোঅর্ডিনেট করতে এবং আপনার কম্পোজ স্ক্রিনে UI স্ট্যাটাস দেখাতে Android ViewModel ক্লাস ব্যবহার করুন:

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()
    }
}

সরাসরি টেক্সট টোকেন এক্সট্র্যাক্ট করুন

কনভারসেশনাল ইন্টারফেসের জন্য যেখানে টুল এক্সিকিউশন সংক্রান্ত বিবরণ UI-তে আলাদাভাবে দেখানোর প্রয়োজন নেই, সেখানে Flow<AgentEvent> ইন্টারফেসে textTokens() এক্সটেনশন ফাংশন ব্যবহার করুন:

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")
            }
        }
    }
}

পরবর্তী ধাপ