# Quickstart

The AI components give your Android app the screens of an AI assistant: a conversation that streams the agent's replies, a prompt composer with dictation and photos, the agent's reasoning and tool calls, and a face for a voice agent. The agent behind them runs on [Stream Agents](https://getstream.io/agents/docs/quickstart/?lang=android): you describe it once, with its instructions, knowledge, tools and voice, and your app talks to it with the agents SDK.

This page shows what you can build, how the pieces fit together, and takes you from an empty project to a working assistant.

## What you can build

| You want to build                                                                               | Start with                                                                                                            |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| An AI-first app, like ChatGPT or Claude: one user, one assistant, many conversations            | This page, then the [Personal assistant](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/personal-assistant/) use case |
| A support agent that answers from your help center, looks up orders and hands over to your team | The [Customer support agent](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/customer-support-agent/) use case         |
| A voice agent the user talks to, with a visual presence and a written transcript                | The [Voice agent](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/voice-agent/) use case                               |
| An agent that shows its work and asks before it acts                                            | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/android/ai-components/reasoning-and-tool-calls/)                            |
| An agent that uses the device: the calendar, the location, a screen of your app                 | The [Personal assistant](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/personal-assistant/) use case                 |
| Replies that are UI, such as a trip plan with a map and a form                                  | [Generative UI](https://getstream.io/chat/docs/sdk/android/ai-components/generative-ui/)                                                  |

Voice runs through all of them. Every composer has a microphone button that dictates into it, and the same agent can hold the conversation out loud: `VoiceSession` puts it on a call with the user, `AIVoiceGlow` or `AIVoiceWave` shows what it's doing, and `AIConversation` writes out what was said.

## How it fits together

Your app never talks to the model, and it doesn't run the agent. It opens a **session** with your agent and asks it things. The agent runs on Stream's router, which calls the model, runs the agent's tools and writes the conversation into the session's Stream Chat channel as it goes, and the components show that channel:

```mermaid
sequenceDiagram
    participant App as Android app
    participant Router as Stream Agents
    participant LLM as Model
    App->>Router: agent("my-agent").chat() with the agents SDK
    App->>Router: responses.create("How do I...?")
    Router->>LLM: The conversation so far
    LLM-->>Router: Reasoning, tool calls, tokens
    Router-->>App: The reply in the session's channel, with its steps
    Note over App: AIConversation shows it
```

- **The agents SDK** (`stream-agents-android-core`) signs the user in, opens and lists sessions, asks, stops a reply, answers the questions of tools that ask first, and runs the tools your app gives the agent on the phone.
- **The AI components** (`stream-chat-android-ai-compose`) show it all: `AIConversation` and `ChatComposer`, the reasoning and tool calls of each reply, and the voice visualizations.
- **Your agent** lives in a folder of plain files that you sync with the Stream CLI: its instructions, knowledge, skills, tools, connectors and models.

For voice, `VoiceSession` starts the agent on a call and joins it from the phone. The agent hears the user and answers out loud, and what is said is written into the session's channel.

## Before you start

- A Stream app, from the [Stream Dashboard](https://getstream.io/signup/?product=chat).
- The beta [Stream CLI](https://getstream.io/cli/docs/install/), which has the `getstream agents` commands.
- An app with `compileSdk` 37, `minSdk` 24 or later, and Kotlin 2.3 or later.

## Step 1: Create the agent

Install the beta CLI, pick your Stream app, and make an agent from the text template:

```sh
curl -fsSL https://getstream.io/cli/beta/install.sh | bash
getstream init                                     # pick or create a Stream app
getstream agents init my-agent --template text     # or --template voice
```

That creates `agents/my-agent/`, with the agent's settings in `agent.yaml` and its system prompt in `instructions.md`. Write what the agent is for in `instructions.md`, then sync it to Stream and try it in the terminal:

```sh
getstream agents sync my-agent
getstream agents test my-agent
```

The [Stream Agents quickstart](https://getstream.io/agents/docs/quickstart/?lang=android) covers the agent's folder: knowledge, skills, tools, connectors and guardrails.

## Step 2: Install the SDKs

Add two libraries to your app: `stream-agents-android-core`, the client for your agent, and `stream-chat-android-ai-compose`, the views. Both come from Stream's Maven repository:

```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven("https://stream-io-repo.com")
    }
}

// build.gradle.kts: needs compileSdk 37 and Kotlin 2.3 or newer
dependencies {
    implementation("io.getstream:stream-agents-android-core:0.2.0")
    implementation("io.getstream:stream-chat-android-ai-compose:7.18.0")
    implementation("androidx.lifecycle:lifecycle-runtime-compose:<version>")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:<version>")
}
```

`stream-agents-android-core` brings Stream Chat and Stream Video with it, which keep the conversation and carry the voice.

Is your agent on a backend of your own? Add only `stream-chat-android-ai-compose`, and give the views the messages of your own channel, as described in [Conversation State](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-state/). The AI components don't depend on the other Chat UI libraries, so they work next to `stream-chat-android-compose` or a UI of your own. If your agent answers with [generative UI](https://getstream.io/chat/docs/sdk/android/ai-components/generative-ui/), also add `stream-chat-android-ai-a2ui` or `stream-chat-android-ai-openui`.

The AI components ship with Stream Chat Android at the same version as the rest of the SDK, from 7.15.0, and need `minSdk` 23 on their own. Earlier versions were released separately, from `stream-chat-android-ai`, as 0.x. The [Compose sample](https://github.com/GetStream/stream-chat-android/tree/main/stream-chat-android-ai-compose-sample) builds a ChatGPT-like app on them with a backend of your own.

The composer can attach photos and dictate. Its photo picker is the system's, which needs no permission. Dictation asks for the microphone the first time the user taps it, and `RECORD_AUDIO` is already in your merged manifest, from Stream Video.

## Step 3: Sign the user in

Create one `StreamAgents` client for your app with your API key, and set the user once they sign in. The user's Stream token comes from your backend, which mints it with your API secret, as described in [authentication](https://getstream.io/docs/platform/authentication/). The client asks for it again when it expires:

```kotlin
val agents: StreamAgents = StreamAgents(apiKey = "your_api_key")

fun signIn(userId: String, name: String) {
    agents.setUser(User(id = userId, name = name)) { yourBackend.streamToken() }
}
```

To try things out before you have a backend, mint a token for a test user with the CLI, `getstream token jane --ttl 2h`, and pass it as `agents.setUser(User(id = "jane"), token = "...")`.

## Step 4: Connect a conversation to the agent

The router writes the conversation into the session's Stream Chat channel: each question, and each reply with its reasoning and tool calls. The views show that channel, so connecting them to the agent is two small types. Add them to your project. `AgentMessage` is one message, as the views show it:

```kotlin
/** A message of the session's channel, as the AI components show it. */
data class AgentMessage(val message: Message) : AIMessage {
    override val id: String = message.id

    // The agent's replies carry `ai_generated: true`.
    override val role: AIMessageRole =
        if (message.extraData["ai_generated"] == true) AIMessageRole.Assistant else AIMessageRole.User
    override val text: String = message.text
    override val isGenerating: Boolean = message.extraData["generating"] == true

    // The reasoning and tool calls the agent writes on its reply, as attachments. Stream Chat
    // lifts `name` out of an attachment's fields, and a tool call's step needs it back.
    override val parts: List<AIMessagePart> = AIMessagePart.parts(
        message.attachments.map { it.type.orEmpty() to it.extraData + ("name" to it.name) },
    )
}
```

`AgentChat` watches the channel, asks the agent, stops it, and says whether a reply is on its way. Its `approver` answers the tools that ask before they run:

```kotlin
/** A conversation with the agent: its channel's messages, and asking and stopping it. */
@OptIn(ExperimentalCoroutinesApi::class)
class AgentChat private constructor(
    val session: AgentSession,
    val approver: AIToolApprover,
    channel: StateFlow<ChannelState?>,
    private val scope: CoroutineScope,
) {
    /** The channel's messages, oldest first, as Stream Chat keeps them up to date. */
    val messages: StateFlow<List<AgentMessage>> = channel
        .flatMapLatest { it?.messages ?: flowOf(emptyList()) }
        .map { messages -> messages.filter { it.deletedAt == null }.map(::AgentMessage) }
        .stateIn(scope, SharingStarted.Eagerly, emptyList())

    /** Whether a reply is on its way, from the first thought to the last word. */
    val isAnswering: StateFlow<Boolean> = combine(session.conversation, messages) { conversation, messages ->
        when (conversation.state) {
            Conversation.State.Responding, is Conversation.State.Working -> true
            else -> messages.lastOrNull()?.isGenerating == true
        }
    }.stateIn(scope, SharingStarted.Eagerly, false)

    /** Asks the agent. The router writes the question into the channel, then the reply. */
    fun send(text: String) {
        scope.launch {
            try {
                session.responses.create(text)
            } catch (_: AgentsException) {
                // Not sent: the user can send it again.
            }
        }
    }

    /** Stops the reply in flight. What was written so far is kept. */
    fun stop() {
        scope.launch {
            try {
                session.interrupt()
            } catch (_: AgentsException) {
                // The reply finished first.
            }
        }
    }

    companion object {
        /** Opens the session's channel. Pass a [scope] on the main thread, such as `viewModelScope`. */
        suspend fun open(context: Context, session: AgentSession, scope: CoroutineScope): AgentChat {
            // Answers the tools that ask before they run, as the signed-in user.
            val userId = agents.backend.user?.id.orEmpty()
            val approver = AIToolApprover(userId, AIClientIdentity.installId(context), scope) { call, allowed ->
                session.decide(call.id, allowed)
            }
            // Connects to Stream Chat as the signed-in user and watches the session's channel.
            return AgentChat(session, approver, session.channelState(context, scope), scope)
        }
    }
}
```

The agent doesn't answer a message sent to the channel, so `send` asks the session, and the router writes the question into the channel itself. `channelState` connects Stream Chat as the user you set in step 3, and keeps the messages up to date, after a reconnect too.

## Step 5: Show the conversation

Open a session with your agent in a `ViewModel`, so the conversation and the user's answers to tools outlive a configuration change, and show it with `AIConversation` and the composer:

```kotlin
class AssistantViewModel(application: Application) : AndroidViewModel(application) {
    private val _chat = MutableStateFlow<AgentChat?>(null)
    val chat: StateFlow<AgentChat?> = _chat.asStateFlow()

    init {
        viewModelScope.launch {
            // Opens a session with the agent you synced in step 1.
            val session = agents.agent("my-agent").chat()
            _chat.value = AgentChat.open(application, session, viewModelScope)
        }
    }
}

@Composable
fun AssistantScreen(viewModel: AssistantViewModel = viewModel()) {
    val chat by viewModel.chat.collectAsStateWithLifecycle()
    val current = chat
    if (current != null) {
        AgentConversation(current, Modifier.fillMaxSize())
    } else {
        Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() }
    }
}

@Composable
fun AgentConversation(chat: AgentChat, modifier: Modifier = Modifier) {
    val messages by chat.messages.collectAsStateWithLifecycle()
    val answering by chat.isAnswering.collectAsStateWithLifecycle()

    Column(modifier) {
        AIConversation(
            messages = messages,
            modifier = Modifier.weight(1f),
            // The agent is on it, and its reply isn't in the channel yet.
            isThinking = answering && messages.lastOrNull()?.role != AIMessageRole.Assistant,
            approver = chat.approver,
        )
        ChatComposer(
            onSendClick = { chat.send(it.text) },
            onStopClick = chat::stop,
            // While the agent answers, the send button is a stop button.
            isGenerating = answering,
        )
    }
}
```

`chat()` throws an `AgentsException` when the session can't be opened, for example when the user isn't set or the agent's name is wrong. Catch it to show an error in place of the progress indicator.

Run the app and ask something. The agent thinks, then its reply writes itself out, with Markdown, code and charts, and with the reasoning and tool calls that led to it. While it writes, the send button is a stop button, and under each finished reply the user can copy and share it.

The [Conversation View](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-view/) guide covers what else the screen can show, such as suggestions while the conversation is empty, ratings and older messages. `send` here passes only the text: to send the photos picked in the composer too, see the [Personal assistant](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/personal-assistant/) use case.

## Step 6: Add voice

The composer already takes voice: the microphone button dictates into it with Android's `SpeechRecognizer`.

To let the user talk to the agent instead, start a voice session. The agent joins a call right away, and the phone joins it with the microphone on. Ask for `RECORD_AUDIO` first:

```kotlin
suspend fun startVoice(context: Context): VoiceSession {
    val voice = VoiceSession.start(context, agents, SessionOptions(agent = "my-agent"))
    voice.join()
    return voice
}
```

`join()` doesn't throw. If joining fails, the error is in `voice.failure`.

Give the agent a face with `AIVoiceGlow`, which moves differently while the agent is connecting, listening, thinking and speaking. The [Voice agent](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/voice-agent/) use case builds the whole screen: the agent's state, the visualizations, mute and hang up, and what was said written out as a conversation. An agent made with `--template voice` also lets you pick its speech-to-text and text-to-speech models, its voice and its greeting.

## Theming

The components take their colors and text styles from the `MaterialTheme` around them, so your app's theme already styles them:

```kotlin
MaterialTheme(colorScheme = lightColorScheme(primary = Color(0xFF6E3FF3))) {
    AssistantScreen()
}
```

To change one component, pass it colors from its `Defaults` object, such as `AIConversationDefaults.colors(...)` or `AIUserMessageDefaults.colors(...)`, and icons from `icons(...)` where it has them. Each component's guide lists its own under **Appearance**.

A component that `AIConversation` or `ChatComposer` draws for you, such as the user's message bubble or the composer's buttons, is a slot of `ChatAiComponentFactory`. Override the slots you want with `CompoundChatAiComponentFactory`, and keep the rest:

```kotlin
CompoundChatAiComponentFactory(
    factory = { current ->
        object : ChatAiComponentFactory by current {
            @Composable
            override fun AIConversationUserMessage(params: AIConversationUserMessageParams) {
                AIUserMessage(
                    text = params.text,
                    attachments = params.attachments,
                    modifier = params.modifier,
                    colors = AIUserMessageDefaults.colors(background = Color(0xFFEDE7FF)),
                )
            }
        }
    },
) {
    AssistantScreen()
}
```

### Localization

Every text the components show, such as the composer's "Ask Assistant" placeholder or "Thought for 12s", is a string resource whose name starts with `stream_ai_compose_`. Override the ones you want in your app's `strings.xml`, and add the same names under `values-<language>` to translate them:

```xml
<string name="stream_ai_compose_composer_input_placeholder">Message the assistant</string>
```

The library ships English only. The names are listed in its [`strings.xml`](https://github.com/GetStream/stream-chat-android/blob/develop/stream-chat-android-ai-compose/src/main/res/values/strings.xml).

## The components

| Component                                                      | What it does                                                          | Guide                                                                                                          |
| -------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `AIConversation`                                               | Lays out a conversation the way AI assistants do                      | [Conversation View](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-view/)                                   |
| `AIMessage`                                                    | One message, as the views show it, made from your channel's messages  | [Conversation State](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-state/)                                 |
| `ChatComposer`, `SpeechToTextButton`                           | Takes what the user writes, with photos and dictation                 | [Composer](https://getstream.io/chat/docs/sdk/android/ai-components/composer/)                                                     |
| `StreamingText`, `AITypingIndicator`                           | Types out Markdown, code and charts as they stream                    | [Streaming Messages](https://getstream.io/chat/docs/sdk/android/ai-components/streaming-messages/)                                 |
| `AIMessageParts`, `StreamingReasoning`, `AIToolApprovalPrompt` | Shows a reply's reasoning and tool calls, and asks before a tool runs | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/android/ai-components/reasoning-and-tool-calls/)                     |
| `AIClientToolRunner`                                           | Runs the tools the agent asks the device to run                       | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/android/ai-components/reasoning-and-tool-calls/#tools-on-the-device) |
| `AIUIHost`                                                     | Shows UI the agent writes in place of a reply's text                  | [Generative UI](https://getstream.io/chat/docs/sdk/android/ai-components/generative-ui/)                                           |
| `AIVoiceGlow`, `AIVoiceWave`                                   | Shows what a voice agent is doing                                     | [Voice Visualizations](https://getstream.io/chat/docs/sdk/android/ai-components/voice-visualizations/)                             |
| `AISuggestions`                                                | Offers conversation starters                                          | [Conversation View](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-view/)                                   |
| `AIOnDeviceModel`                                              | Answers with Gemini Nano on the device when your agent can't          | [Conversation State](https://getstream.io/chat/docs/sdk/android/ai-components/conversation-state/#answer-on-the-device)            |
| `ChatAiComponentFactory`, `*Defaults`                          | Sets the colors, icons and slots of all of them                       | [Theming](#theming)                                                                                            |

## Next steps

- [Customer support agent](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/customer-support-agent/): answers from your help center, order lookups, a refund the user confirms, and a handover to your team.
- [Personal assistant](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/personal-assistant/): a list of conversations, the calendar as a tool on the phone, and a voice mode.
- [Voice agent](https://getstream.io/chat/docs/sdk/android/ai-components/use-cases/voice-agent/): a voice conversation with the agent as a glowing globe, and what was said written out.
- [Stream Agents](https://getstream.io/agents/docs/quickstart/?lang=android): the agent itself, its tools, knowledge, connectors and phone numbers.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/ai-components/](https://getstream.io/chat/docs/sdk/android/ai-components/).