MobileAgentSDK は、C++ MobileAgent SDK をラップし、Kotlin/Java フレンドリーなインターフェイスを提供する Android ライブラリです。WebSocket を介して MobileAgent サーバーに接続し、エージェントタスクの実行、管理、監視を行います。
SDKのダウンロード
使用方法
AARのインポート
生成された AAR ファイルをプロジェクトにコピーし、build.gradle ファイルに次の依存関係を追加します。
dependencies {
implementation(files('libs/sdk-release.aar'))
}基本的な使用方法
オプション1:フローの使用 (推奨)
フローは、Kotlin コルーチンに適したリアクティブプログラミングスタイルを提供します。
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentEvent
import com.wuying.mobileagentsdk.MobileAgentState
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collectLatest
// try-with-resources を使用してライフサイクルを自動的に管理します。
MobileAgentSdk().use { sdk ->
// コルーチンでイベントを収集します。
lifecycleScope.launch {
sdk.events.collectLatest { event ->
when (event) {
is MobileAgentEvent.StateChanged -> {
when (event.state) {
MobileAgentState.CONNECTED -> {
Log.d("SDK", "Connected")
// タスクを実行します。
sdk.executeTask("Please help me open settings", 10)
}
MobileAgentState.DISCONNECTED -> {
Log.d("SDK", "Disconnected")
}
else -> {}
}
}
is MobileAgentEvent.TaskResult -> {
Log.d("SDK", "Task complete: ${event.resultText}")
}
is MobileAgentEvent.Stream -> {
Log.d("SDK", "Stream data: ${event.content}")
}
else -> {}
}
}
}
// 特定のタイプのイベントのみを収集することもできます。
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.StateChanged>()
.collect { event ->
Log.d("SDK", "State: ${event.state}")
}
}
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
Log.d("SDK", "Task result: ${event.resultText}")
}
}
// サーバーに接続します。
sdk.connect("wss://localhost:30005/ws")
// use ブロックの最後に SDK は自動的に破棄されます。
}オプション2:コールバックの使用
このアプローチは、コルーチンを使用しないプロジェクト向けです。
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentCallback
import com.wuying.mobileagentsdk.MobileAgentState
MobileAgentSdk().use { sdk ->
// コールバックを設定します。
sdk.setCallback(object : MobileAgentCallback {
override fun onStateChanged(state: MobileAgentState) {
when (state) {
MobileAgentState.CONNECTED -> {
Log.d("SDK", "Connected")
sdk.executeTask("Please help me open settings", 10)
}
MobileAgentState.DISCONNECTED -> {
Log.d("SDK", "Disconnected")
}
else -> {}
}
}
override fun onTaskResult(
taskId: String,
result: String,
resultText: String,
success: Boolean,
actualSteps: Int
) {
Log.d("SDK", "Task complete: $resultText")
}
override fun onStream(
taskId: String,
step: Int,
agent: String,
contentType: String,
content: String
) {
Log.d("SDK", "Stream data: $content")
}
// 必要に応じて他のコールバックを実装します。
})
// サーバーに接続します。
sdk.connect("wss://localhost:30005/ws")
}ライフサイクル管理
SDK は AutoCloseable インターフェイスを実装しており、try-with-resources パターンをサポートしています。
// 推奨: ライフサイクルを自動的に管理します。
MobileAgentSdk().use { sdk ->
sdk.connect("wss://localhost:30005/ws")
// SDK を使用...
} // close() を自動的に呼び出してリソースを解放します。
// または、ライフサイクルを手動で管理します。
val sdk = MobileAgentSdk()
try {
sdk.connect("wss://localhost:30005/ws")
// SDK を使用...
} finally {
sdk.close()
}API
接続管理
connect(url: String)
WebSocket URL を使用してサーバーに接続します。
sdk.connect("wss://localhost:30005/ws")connectWithTicket(ticket: String)
チケットを使用して接続します (サーバー側のサポートが必要です)。
sdk.connectWithTicket("your-ticket-here")disconnect()
サーバーから切断します。
sdk.disconnect()タスク管理
executeTask(task: String, maxSteps: Int): String
タスクを実行し、タスク ID を返します。
val taskId = sdk.executeTask("Please help me open the settings app", 10)pauseTask(taskId: String)
タスクを一時停止します。
sdk.pauseTask(taskId)resumeTask(taskId: String)
タスクを再開します。
sdk.resumeTask(taskId)cancelTask(taskId: String)
タスクをキャンセルします。
sdk.cancelTask(taskId)状態クエリ
getState(): MobileAgentState
現在の接続状態を取得します。
val state = sdk.getState()
when (state) {
MobileAgentState.CONNECTED -> // 接続済み
MobileAgentState.CONNECTING -> // 接続中
MobileAgentState.DISCONNECTED -> // 切断済み
}フロー API
SDK は、すべてのイベントタイプを発行する events フローを提供します。filterIsInstance を使用して、特定のイベントをフィルターできます。
// すべてのイベントを収集します。
sdk.events.collect { event ->
when (event) {
is MobileAgentEvent.StateChanged -> // 状態の変更を処理します。
is MobileAgentEvent.TaskResult -> // タスク結果を処理します。
is MobileAgentEvent.Stream -> // ストリームデータを処理します。
// ...
}
}
// 特定のタイプのイベントのみを収集します。
sdk.events
.filterIsInstance<MobileAgentEvent.StateChanged>()
.collect { event ->
// 状態の変更のみを処理します。
}イベントタイプ:
イベントタイプ | 説明 |
| 接続状態の変更 |
| ToDo リストの更新 |
| タスクの開始 |
| ステップの開始 |
| ストリームデータの受信 |
| ステップの終了 |
| タスク結果の受信 |
コールバック API
MobileAgentCallback インターフェイスは、以下のコールバックメソッドを提供します。すべてのメソッドにはデフォルトの空実装が用意されているため、必要なものだけをオーバーライドできます。
メソッド | 説明 |
| 接続状態の変更 |
| ToDo リストの更新 |
| タスクの開始 |
| ステップの開始 |
| ストリームデータの受信 |
| ステップの終了 |
| タスク結果の受信 |
| 未解析の生JSONメッセージ |
データクラス
MobileAgentState
接続状態を表す enum:
enum class MobileAgentState {
DISCONNECTED, // 切断済み
CONNECTING, // 接続中
CONNECTED // 接続済み
}TodoItem
ToDo アイテムを表すデータクラス:
data class TodoItem(
val id: String,
val title: String,
val status: String,
val details: String
)MobileAgentEvent
すべての可能なイベントタイプを表すシールクラス:
sealed class MobileAgentEvent {
data class StateChanged(val state: MobileAgentState)
data class TodoWrite(val todos: Array<TodoItem>, val count: Int)
data class TaskStart(val taskId: String, val task: String, val maxSteps: Int)
data class StepStart(val taskId: String, val step: Int)
data class Stream(val taskId: String, val step: Int, val agent: String, val contentType: String, val content: String)
data class StepEnd(val taskId: String, val step: Int, val status: String, val summary: String)
data class TaskResult(val taskId: String, val result: String, val resultText: String, val success: Boolean, val actualSteps: Int)
}高度な使用方法
エージェントの思考の解析
MobileAgent は、ストリーム出力の <conclusion> タグを使用して、エージェントの思考プロセスをラップします。このコンテンツは Stream イベントから抽出できます。
<conclusion> のフォーマット
エージェントは、XML タグを使用して出力内の思考プロセスをマークします。
<conclusion>This is the agent's thought process...</conclusion>Kotlinでの実装
次のコードは、思考コンテンツを受信しながら解析する方法を示しています。
import com.wuying.mobileagentsdk.MobileAgentSdk
import com.wuying.mobileagentsdk.MobileAgentEvent
class ConclusionParser {
private val conclusionBuffer = StringBuilder()
private var inConclusion = false
/**
* ストリームデータを処理して <conclusion> タグからコンテンツを抽出します。
*/
fun processStream(content: String): String? {
val result = StringBuilder()
for (char in content) {
if (!inConclusion) {
// conclusion の内部ではないため、<conclusion> の開始をチェックします。
conclusionBuffer.append(char)
// <conclusion> をチェックします。
if (conclusionBuffer.length >= 12) {
if (conclusionBuffer.endsWith("<conclusion>")) {
inConclusion = true
conclusionBuffer.clear()
result.append("[Thought] ")
} else if (conclusionBuffer.length > 12) {
// 次のチェックのために最後の 11 文字を保持します。
conclusionBuffer.delete(0, conclusionBuffer.length - 11)
}
}
} else {
// conclusion の内部です。終了タグをチェックします。
conclusionBuffer.append(char)
// </conclusion> をチェックします。
if (conclusionBuffer.length >= 13) {
if (conclusionBuffer.endsWith("</conclusion>")) {
// 終了タグが見つかりました。
inConclusion = false
val conclusionContent = conclusionBuffer.toString()
.dropLast(13) // </conclusion> を削除します。
.trim()
if (conclusionContent.isNotEmpty()) {
result.append(conclusionContent).append("\n")
}
conclusionBuffer.clear()
continue
} else if (conclusionBuffer.length > 13) {
// 次のチェックのために最後の 12 文字を保持します。
conclusionBuffer.delete(0, conclusionBuffer.length - 12)
}
}
// 終了タグの一部でなければ、すぐに出力します。
if (!couldBeEndTag(conclusionBuffer.toString())) {
result.append(char)
}
}
}
return if (result.isNotEmpty()) result.toString() else null
}
/**
* バッファーの末尾が終了タグのプレフィックスである可能性があるかどうかをチェックします。
*/
private fun couldBeEndTag(buffer: String): Boolean {
val prefixes = listOf(
"<", "</", "</c", "</co", "</con", "</conc",
"</concl", "</conclu", "</conclus", "</conclusi",
"</conclusio", "</conclusion"
)
return prefixes.any { buffer.endsWith(it) }
}
/**
* パーサーの状態をリセットします。
*/
fun reset() {
conclusionBuffer.clear()
inConclusion = false
}
}
// 使用例
MobileAgentSdk().use { sdk ->
val parser = ConclusionParser()
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.Stream>()
.collect { event ->
// 解析結果を出力します。
val output = parser.processStream(event.content)
if (output != null) {
print(output)
}
}
}
sdk.connect("wss://localhost:30005/ws")
}簡易バージョン
リアルタイムの出力が必要ない場合は、コンテンツを完全に受信した後に解析できます。
/**
* 文字列全体から <conclusion> タグ内のすべてのコンテンツを抽出します。
*/
fun extractConclusions(content: String): List<String> {
val conclusions = mutableListOf<String>()
val regex = "<conclusion>(.*?)</conclusion>".toRegex(RegexOption.DOT_MATCHES_ALL)
for (match in regex.findAll(content)) {
val conclusion = match.groupValues[1].trim()
if (conclusion.isNotEmpty()) {
conclusions.add(conclusion)
}
}
return conclusions
}
// 使用例
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
// タスク結果から思考プロセスを抽出します。
val conclusions = extractConclusions(event.resultText)
println("Agent's thought process:")
conclusions.forEachIndexed { index, conclusion ->
println("${index + 1}. $conclusion")
}
}
}出力
[思考] ユーザーは設定アプリを開きたいようです。パッケージ名とアクティビティを見つける必要があります。
[思考] システムアプリリストを照会して、設定アプリを見つけました: com.android.settings/.Settings
[思考] これから設定アプリを起動します。
タスク完了: 設定アプリを正常に開きました。フローとコールバックの併用
両方のアプローチを干渉することなく同時に使用できます。
MobileAgentSdk().use { sdk ->
// フローを使用してメインロジックを処理します。
lifecycleScope.launch {
sdk.events
.filterIsInstance<MobileAgentEvent.TaskResult>()
.collect { event ->
// タスク結果を処理します。
}
}
// ログ記録にコールバックを使用します。
sdk.setCallback(object : MobileAgentCallback {
override fun onRawMessage(rawJson: String) {
// デバッグ用に生のメッセージをログに記録します。
Log.d("SDK_RAW", rawJson)
}
})
sdk.connect("wss://localhost:30005/ws")
}エラー処理
try {
MobileAgentSdk().use { sdk ->
sdk.connect("wss://localhost:30005/ws")
}
} catch (e: IllegalStateException) {
Log.e("SDK", "Failed to create SDK", e)
} catch (e: Exception) {
Log.e("SDK", "Connection error", e)
}SDKのステータスの確認
val sdk = MobileAgentSdk()
// SDK が破棄済みかどうかを確認します。
if (sdk.isDestroyed()) {
Log.w("SDK", "The SDK has been destroyed.")
}
// SDK が有効かどうかを確認します。
sdk.checkNotDestroyed() // 破棄済みの場合は例外をスローします。ProGuardの設定
コードの難読化を有効にする場合は、SDK のネイティブメソッドを保持する必要があります。SDK にはすでに proguard-rules.pro ファイルが含まれているため、通常は追加の設定は不要です。
手動で設定する必要がある場合:
# MobileAgentSDK
-keep class com.wuying.mobileagentsdk.** { *; }
-keepclassmembers class com.wuying.mobileagentsdk.MobileAgentSdk {
private native <methods>;
}依存関係
SDK のランタイム依存関係は最小限です。
dependencies {
// 唯一のランタイム依存関係
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}軽量性を維持するため、SDK には AppCompat や Material などの UI フレームワークは含まれていません。