> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Kotlin SDK

> **Info**
>
> ElevenAgentsの仕組みについては、[ElevenAgentsの概要](/docs/ja/eleven-agents/overview)を
> ご覧ください。

## インストール

アプリレベルの`build.gradle`ファイルに以下の依存関係を追加して、AndroidプロジェクトにElevenLabs SDKを追加します。

**`build.gradle.kts`**

```kotlin build.gradle.kts
dependencies {
    // ElevenLabs Agents SDK (Android)
    implementation("io.elevenlabs:elevenlabs-android:<latest>")

    // Kotlin coroutines, AndroidX, etc., as needed by your app
}
```

> **Tip**
>
> このSDKを使用したAndroidアプリの例は
> [こちら](https://github.com/elevenlabs/elevenlabs-android/tree/main/example-app)で確認できます。

## 要件

* Android APIレベル21（Android 5.0）以上
* API呼び出し用のインターネット権限
* 音声入力用のマイク権限
* HTTPS呼び出し用のネットワークセキュリティ設定

## セットアップ

### マニフェストの設定

必要な権限を`AndroidManifest.xml`に追加します。

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```

### 実行時の権限

Android 6.0（APIレベル23）以降では、実行時にマイク権限をリクエストする必要があります。

```kotlin
import android.Manifest
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

private fun requestMicrophonePermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
        != PackageManager.PERMISSION_GRANTED) {

        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
            // Show explanation to the user
            showPermissionExplanationDialog()
        } else {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.RECORD_AUDIO),
                MICROPHONE_PERMISSION_REQUEST_CODE
            )
        }
    }
}
```

## 使用方法

`Application`クラスまたはメインアクティビティでElevenLabs SDKを初期化します。

以下のいずれかで会話セッションを開始します。

* パブリックエージェント：`agentId`を渡す
* プライベートエージェント：バックエンドで発行した`conversationToken`を渡す（APIキーをクライアントに公開しないでください）。

```kotlin
import io.elevenlabs.ConversationClient
import io.elevenlabs.ConversationConfig
import io.elevenlabs.ConversationSession
import io.elevenlabs.ClientTool
import io.elevenlabs.ClientToolResult

// Start a public agent session (token generated for you)
val config = ConversationConfig(
    agentId = "<your_public_agent_id>", // OR conversationToken = "<token>"
    userId = "your-user-id",
    // Optional callbacks
    onConnect = { conversationId ->
        // Called when the conversation is connected and returns the conversation ID. You can access conversationId via session.getId() too
    },
    onMessage = { source, messageJson ->
        // Raw JSON messages from data channel; useful for logging/telemetry
    },
    onModeChange = { mode ->
        // "speaking" | "listening" — drive UI indicators
    },
    onStatusChange = { status ->
        // "connected" | "connecting" | "disconnected"
    },
    onCanSendFeedbackChange = { canSend ->
        // Enable/disable thumbs up/down buttons for feedback reporting
    },
    onUnhandledClientToolCall = { call ->
        // Agent requested a client tool not registered on the device
    },
    onVadScore = { score ->
        // Voice Activity Detection score, range from 0 to 1 where higher values indicate higher confidence of speech
    },
    onAudioAlignment = { alignment ->
        // Character-level timing data for synchronized text display
        val chars = alignment["chars"] as? List<*>
        val startTimes = alignment["char_start_times_ms"] as? List<*>
        val durations = alignment["char_durations_ms"] as? List<*>
        Log.d("ExampleApp", "Audio alignment: $chars")
    },
    // List of client tools the agent can invoke
    clientTools = mapOf(
        "logMessage" to object : ClientTool {
            override suspend fun execute(parameters: Map<String, Any>): ClientToolResult {
                val message = parameters["message"] as? String

                Log.d("ExampleApp", "[INFO] Client Tool Log: $message")
                return ClientToolResult.success("Message logged successfully")
            }
        }
    ),
)

// In an Activity context
val session: ConversationSession = ConversationClient.startSession(config, this)
```

ElevenAgentsにはマイクへのアクセスが必要です。特に実行時の権限が必要なAndroid 6.0以降では、会話を開始する前にアプリのUIで権限について説明し、リクエストすることを検討してください。

> **Note**
>
> サーバー側でツールが`expects_response=false`に設定されている場合は、`execute`から`null`を返し、
> ツールの結果をエージェントに送信しないようにします。

## パブリックエージェントとプライベートエージェント

* **パブリックエージェント**（認証なし）：`ConversationConfig`で`agentId`を指定して初期化します。SDKはデバイス上のAPIキーを必要とせず、ElevenLabsから会話トークンをリクエストします。
* **プライベートエージェント**（認証あり）：`ConversationConfig`で`conversationToken`を指定して初期化します。サーバーがElevenLabs APIキーを使用して、ElevenLabsから会話トークンをリクエストします。

> **Error**
>
> APIキーをクライアントに埋め込まないでください。簡単に抽出され、悪用されるおそれがあります。

## クライアントツール

クライアントツールを登録すると、エージェントがデバイス上のローカル機能を呼び出せるようになります。

```kotlin
val config = ConversationConfig(
    agentId = "<public_agent>",
    clientTools = mapOf(
        "logMessage" to object : io.elevenlabs.ClientTool {
            override suspend fun execute(parameters: Map<String, Any>): io.elevenlabs.ClientToolResult? {
                val message = parameters["message"] as? String ?: return io.elevenlabs.ClientToolResult.failure("Missing 'message'")

                android.util.Log.d("ClientTool", "Log: $message")
                return null // No response needed for fire-and-forget tools
            }
        }
    )
)
```

エージェントが`client_tool_call`を発行すると、SDKは対応するツールを実行し、`client_tool_result`で応答します。ツールが登録されていない場合は、`onUnhandledClientToolCall`が呼び出され、応答が想定されていれば失敗結果がエージェントに返されます。

### コールバックの概要

* **onConnect** - WebRTC接続が確立されたときに呼び出されます。会話IDを返します。
* **onMessage** - 新しいメッセージを受信したときに呼び出されます。ユーザー音声の暫定または最終文字起こし、LLMが生成した応答、デバッグメッセージなどが含まれます。ソース（`"ai"`または`"user"`）と生のJSONメッセージを提供します。
* **onModeChange** - 会話モードが変更されたときに呼び出されます。エージェントが話している（`"speaking"`）か、聞いている（`"listening"`）かを示す際に役立ちます。
* **onStatusChange** - 会話ステータスが変更されたときに呼び出されます（`"connected"`、`"connecting"`、または`"disconnected"`）。
* **onCanSendFeedbackChange** - フィードバック送信の可否が変わったときに呼び出されます。フィードバックボタンを有効または無効にします。
* **onUnhandledClientToolCall** - エージェントがデバイスに登録されていないクライアントツールをリクエストしたときに呼び出されます。
* **onVadScore** - 音声アクティビティ検出スコアが変わったときに呼び出されます。範囲は0から1で、値が高いほど音声である確信度が高くなります。
* **onAudioAlignment** - オーディオアラインメントデータを受信したときに呼び出され、エージェント音声の文字単位のタイミング情報を提供します。

> **Warning**
>
> すべてのクライアントイベントが、エージェントでデフォルトで有効になっているわけではありません。コールバックを有効にしても
> イベントを受信できない場合は、ElevenLabsエージェントで対応するイベントが
> 有効になっていることを確認してください。ElevenLabsダッシュボードのエージェント設定にある「Advanced」タブで設定できます。

### メソッド

#### startSession

`startSession`メソッドはWebRTC接続を開始し、マイクを使用してElevenLabs Agentsエージェントとの通信を開始します。

##### パブリックエージェント

パブリックエージェント（認証が有効になっていないエージェント）の場合は、`agentId`のみが必要です。エージェントIDは[ElevenLabs UI](https://elevenlabs.io/app/agents)から取得できます。

```kotlin
val session = ConversationClient.startSession(
    config = ConversationConfig(
        agentId = "your-agent-id"
    ),
    context = this
)
```

##### プライベートエージェント

プライベートエージェントの場合は、ElevenLabs APIから取得した`conversationToken`を渡す必要があります。このトークンの生成にはElevenLabs APIキーが必要です。

> **Tip**
>
> `conversationToken`の有効期限は10分です。

```typescript maxLines=0
// Server-side token generation (Node.js example)

app.get("/conversation-token", yourAuthMiddleware, async (req, res) => {
  const response = await fetch(
    `https://api.elevenlabs.io/v1/convai/conversation/token?agent_id=${process.env.AGENT_ID}`,
    {
      headers: {
        // Requesting a conversation token requires your ElevenLabs API key
        // Do NOT expose your API key to the client!
        "xi-api-key": process.env.ELEVENLABS_API_KEY,
      },
    }
  );

  if (!response.ok) {
    return res.status(500).send("Failed to get conversation token");
  }

  const body = await response.json();
  res.send(body.token);
});
```

次に、トークンを`startSession`メソッドに渡します。プライベートエージェントでは`conversationToken`のみが必要です。

```kotlin

// Get conversation token from your server
val conversationToken = fetchConversationTokenFromServer()

// For private agents, pass in the conversation token
val session = ConversationClient.startSession(
    config = ConversationConfig(
        conversationToken = conversationToken
    ),
    context = this
)
```

必要に応じて、会話内でユーザーを識別するためのユーザーIDを渡せます。これは独自の顧客識別子にできます。サーバーに送信される会話開始データに含まれます。

```kotlin
val session = ConversationClient.startSession(
    config = ConversationConfig(
        agentId = "your-agent-id",
        userId = "your-user-id"
    ),
    context = this
)
```

#### endSession

会話を手動で終了するメソッドです。接続を切断して会話を終了します。

```kotlin
session.endSession()
```

#### sendUserMessage

アクティブな会話中にエージェントへテキストメッセージを送信します。エージェントからの応答がトリガーされます。

```kotlin
session.sendUserMessage("Hello, how can you help me?")
```

#### sendContextualUpdate

応答をトリガーしないコンテキスト情報をエージェントに送信します。

```kotlin
session.sendContextualUpdate(
    "User navigated to the profile page. Consider this for next response."
)
```

#### sendFeedback

会話品質に関するフィードバックを提供します。これによりエージェントのパフォーマンス向上に役立ちます。フィードバックが許可されているときに高評価／低評価のUIを有効にするには、`onCanSendFeedbackChange`を使用します。

```kotlin
// Positive feedback
session.sendFeedback(true)

// Negative feedback
session.sendFeedback(false)
```

#### sendUserActivity

中断を防ぐため、ユーザーアクティビティをエージェントに通知します。ユーザーがアプリを操作中で、エージェントに発話を一時停止させたい場合、たとえばチャットでユーザーが入力中の場合に役立ちます。

エージェントはこのシグナルを受信した後、約2秒間発話を一時停止します。

```kotlin
session.sendUserActivity()
```

#### getId

会話IDを取得します。

```kotlin
val conversationId = session.getId()
Log.d("Conversation", "Conversation ID: $conversationId")
// e.g., "conv_123"
```

#### ミュート／ミュート解除

```kotlin
session.toggleMute()
session.setMicMuted(true)   // mute
session.setMicMuted(false)  // unmute
```

`session.isMuted`を監視し、UIラベルを「ミュート」と「ミュート解除」の間で更新します。

### プロパティ

#### status

現在の会話ステータスを取得します。

```kotlin
val status = session.status
Log.d("Conversation", "Current status: $status")
// Values: DISCONNECTED, CONNECTING, CONNECTED
```

## ProGuard／R8

縮小／難読化を行う場合は、GsonモデルとLiveKitが保持されるようにしてください。ルール例（必要に応じて調整）：

```proguard
-keep class io.elevenlabs.** { *; }
-keep class io.livekit.** { *; }
-keepattributes *Annotation*
```

## トラブルシューティング

* 実行時にマイク権限が付与されていることを確認してください
* 再接続が停止する場合は、アプリが`session.endSession()`を呼び出していること、および再接続前に新しいセッションインスタンスを開始していることを確認してください
* エミュレーターでは、オーディオ入力／出力ルートが動作していることを確認してください。物理デバイスのほうが安定して動作する傾向があります

## 実装例

実装例については、[ElevenLabs Android SDKリポジトリ](https://github.com/elevenlabs/elevenlabs-android/tree/main/example-app)のサンプルアプリをご覧ください。このアプリでは以下を紹介しています。

* ワンタップでの接続／切断
* 発話中／リスニング中のインジケーター
* UIで有効／無効を切り替えるフィードバックボタン
* `sendUserActivity()`による入力中インジケーター
* 入力欄からのコンテキストメッセージとユーザーメッセージ
* マイクのミュート／ミュート解除ボタン