Trong quy trình làm việc của tác nhân, lớp AgentRunner và giao diện AgentSession sẽ phát ra các luồng sự kiện phản ứng thể hiện quy trình suy luận, lệnh gọi công cụ và phản hồi bằng văn bản của mô hình. Trang này minh hoạ cách thu thập sự kiện và kết nối sự kiện với giao diện người dùng bằng Kotlin Coroutines và Jetpack Compose.
Tìm hiểu giao diện AgentEvent
Phương thức AgentSession.runAsync() và AgentRunner.runSingleTurnAsync() trả về một luồng Flow<AgentEvent>. Thay vì đợi toàn bộ vòng lặp suy luận kết thúc,
ứng dụng của bạn sẽ nhận được thông tin cập nhật theo thời gian thực khi tác nhân thực thi:
Loại sự kiện |
Nội dung mô tả |
Thao tác thông thường trên giao diện người dùng |
|
Phát ra các đoạn văn bản gia tăng ( |
Thêm |
|
Được phát ra khi Gemini Nano chọn một công cụ và chuẩn bị thực thi công cụ đó (cung cấp |
Hiển thị chỉ báo đang thực hiện (ví dụ: "Đang kiểm tra trạng thái đơn đặt hàng..."). |
|
Được phát ra khi ứng dụng hoàn tất việc thực thi hàm công cụ (cung cấp |
Chuyển trạng thái của công cụ sang đã hoàn tất. |
Thu thập sự kiện trong một lớp ViewModel
Sử dụng lớp ViewModel của Android để điều phối việc gửi câu lệnh và hiển thị trạng thái giao diện người dùng cho màn hình 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()
}
}
Trích xuất trực tiếp mã thông báo văn bản
Đối với giao diện trò chuyện mà bạn không cần hiển thị riêng thông tin chi tiết về việc thực thi công cụ trong giao diện người dùng, hãy sử dụng hàm mở rộng textTokens() trên giao diện 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)
}
Hiển thị trong Jetpack Compose
Sử dụng trạng thái AgentUiState trong một hàm composable để hiển thị văn bản được truyền trực tuyến và hoạt động của công cụ trực tiếp:
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")
}
}
}
}
Các bước tiếp theo
- Tìm hiểu cách xác định công cụ trong phần Xác định công cụ.
- Quản lý bộ nhớ phiên và xử lý các thao tác xoay trong phần Quản lý phiên và vòng đời.