ในระหว่างเวิร์กโฟลว์แบบ Agentic คลาส AgentRunner และอินเทอร์เฟซ AgentSession จะส่งสตรีมเหตุการณ์แบบรีแอ็กทีฟที่แสดงถึงกระบวนการให้เหตุผล การเรียกใช้เครื่องมือ และการตอบกลับแบบข้อความของโมเดล หน้านี้แสดงวิธีรวบรวมเหตุการณ์และเชื่อมต่อกับ UI โดยใช้ Kotlin Coroutine และ Jetpack Compose
ทำความเข้าใจอินเทอร์เฟซของ AgentEvent
เมธอด AgentSession.runAsync() และ AgentRunner.runSingleTurnAsync() จะแสดงผลสตรีม Flow<AgentEvent> แทนที่จะรอให้ลูปการให้เหตุผลทั้งหมดเสร็จสิ้น แอปของคุณจะได้รับการอัปเดตแบบเรียลไทม์เมื่อ Agent ทำงาน ดังนี้
ประเภทเหตุการณ์ |
คำอธิบาย |
การดำเนินการ UI ทั่วไป |
|
แสดงข้อความที่เพิ่มขึ้นทีละส่วน ( |
เพิ่ม |
|
แสดงเมื่อ Gemini Nano เลือกเครื่องมือและเตรียมดำเนินการ (แสดง |
แสดงสัญญาณบอกสถานะว่ากำลังดำเนินการ (เช่น "กำลังตรวจสอบสถานะการสั่งซื้อ...") |
|
แสดงเมื่อแอปเรียกใช้ฟังก์ชันเครื่องมือเสร็จสิ้น (แสดง |
เปลี่ยนสถานะเครื่องมือเป็นเสร็จสมบูรณ์ |
รวบรวมเหตุการณ์ในคลาส ViewModel
ใช้คลาส ViewModel ของ Android เพื่อประสานงานการส่งพรอมต์และแสดงสถานะ UI ต่อหน้าจอ 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()
}
}
ดึงโทเค็นข้อความโดยตรง
สำหรับอินเทอร์เฟซแบบสนทนาที่ไม่จำเป็นต้องแสดงรายละเอียดการดำเนินการของเครื่องมือแยกต่างหากใน UI ให้ใช้ฟังก์ชันส่วนขยาย 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")
}
}
}
}
ขั้นตอนถัดไป
- ดูวิธีระบุเครื่องมือได้ในระบุเครื่องมือ
- จัดการหน่วยความจำของเซสชันและจัดการการหมุนเวียนในจัดการเซสชันและวงจร