Podczas Agentic Workflows klasa AgentRunner i interfejs AgentSession emitują reaktywne strumienie zdarzeń reprezentujące proces rozumowania modelu, wywołania narzędzi i odpowiedzi tekstowe. Na tej stronie dowiesz się, jak zbierać zdarzenia i łączyć je z interfejsem za pomocą współprogramów w języku Kotlin i Jetpack Compose.
Omówienie interfejsu AgentEvent
Metody AgentSession.runAsync() i AgentRunner.runSingleTurnAsync() zwracają strumień Flow<AgentEvent>. Zamiast czekać na zakończenie całej pętli rozumowania, aplikacja otrzymuje aktualizacje w czasie rzeczywistym, gdy agent wykonuje działania:
Typ zdarzenia |
Opis |
Typowe działanie w interfejsie |
|
Emituje przyrostowe fragmenty tekstu ( |
Dołącz |
|
Emitowane, gdy Gemini Nano wybiera narzędzie i przygotowuje się do jego wykonania (udostępnia |
Wyświetl wskaźnik postępu (np. „Sprawdzam stan zamówienia…”). |
|
Emitowane, gdy aplikacja zakończy wykonywanie funkcji narzędzia (zawiera |
Zmień stan narzędzia na „Ukończone”. |
Zbieranie zdarzeń w klasie ViewModel
Użyj klasy ViewModel Androida, aby koordynować wysyłanie promptów i udostępniać stan interfejsu ekranom 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()
}
}
Wyodrębnianie tokenów tekstowych bezpośrednio
W przypadku interfejsów konwersacyjnych, w których szczegóły wykonywania narzędzia nie muszą być wyświetlane osobno w interfejsie, użyj funkcji rozszerzenia textTokens() w interfejsie 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)
}
Renderowanie w Jetpack Compose
Wykorzystaj stan AgentUiState w funkcji typu „composable”, aby renderować tekst przesyłany strumieniowo i aktywność narzędzia na żywo:
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")
}
}
}
}
Dalsze kroki
- Dowiedz się, jak definiować narzędzia, w artykule Definiowanie narzędzi.
- Zarządzaj pamięcią sesji i obsługuj rotacje w sekcji Zarządzanie sesjami i cyklem życia.