Agentic Workflows 中、AgentRunner クラスと AgentSession インターフェースは、モデルの推論プロセス、ツールの呼び出し、テキスト レスポンスを表すリアクティブ イベント ストリームを出力します。このページでは、Kotlin コルーチンと Jetpack Compose を使用してイベントを収集し、UI に接続する方法について説明します。
AgentEvent インターフェースについて
AgentSession.runAsync() メソッドと AgentRunner.runSingleTurnAsync() メソッドは、Flow<AgentEvent> ストリームを返します。推論ループ全体が完了するのを待つのではなく、エージェントの実行中にリアルタイムで更新を受け取ります。
イベント タイプ |
説明 |
一般的な UI アクション |
|
増分テキスト チャンク( |
|
|
Gemini Nano がツールを選択して実行の準備をするときに生成されます( |
進行状況インジケーター(「注文ステータスを確認しています...」など)を表示します。 |
|
アプリがツール関数の実行を完了したときに出力されます( |
ツールのステータスを [完了] に変更します。 |
ViewModel クラスでイベントを収集する
Android の ViewModel クラスを使用して、プロンプトのディスパッチを調整し、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 に個別に表示する必要がない会話型インターフェースの場合は、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")
}
}
}
}
次のステップ
- ツールの定義方法については、ツールを定義するをご覧ください。
- セッションとライフサイクルを管理するで、セッション メモリを管理し、回転を処理する。