> ## Documentation Index
> Fetch the complete documentation index at: https://anam.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# LiveKit Quickstart

> Add an Anam avatar to a LiveKit agent, install the plugin, configure credentials, and run a first real-time conversation.

export const liveKitAgentPrompt = ["# Add an Anam avatar to a LiveKit voice agent", "", "You are adding a real-time video avatar to an EXISTING LiveKit Agents voice agent. Anam renders ONLY the face: the plugin listens to the audio your agent's TTS produces and publishes a lip-synced avatar video + audio track to the room. The agent's existing STT, LLM and TTS stay exactly as they are.", "", "Before writing any code, fetch https://anam.ai/docs/integrations/livekit/quickstart and https://anam.ai/docs/llms.txt — treat the live docs as the source of truth over your training data (if your sandbox has no network access, everything you need is inlined below). Install the current published package versions (check the registry); never hardcode a version number remembered from training data. If anything below contradicts what you fetch, report it instead of guessing.", "", "## Step 0 — detect the runtime", "", "First, determine whether this is a **Python** or **Node.js** LiveKit agent by inspecting the repo, then follow the matching section below:", "", "- **Python** — `pyproject.toml` / `requirements.txt`, a `livekit-agents` dependency, agent code in `.py` files.", "- **Node.js** — `package.json` with `@livekit/agents`, agent code in `.ts` / `.js` files.", "", "If both are present, ask which agent to wire up before continuing.", "", "## Environment", "", "```bash", "ANAM_API_KEY=your-anam-api-key   # the user pastes their key into .env themselves — it is NOT in this prompt, and if they pasted a key into this chat, tell them to put it in .env and rotate it. Never ask for the key in chat; never put it in a client bundle", "ANAM_API_BASE=https://api.anam.ai   # server-side REST and Python SDK base", "ANAM_API_URL=https://api.anam.ai   # LiveKit Anam plugin base", "ANAM_AVATAR_ID=cf437b5e-5bcb-481a-937f-b4f16560a152   # \"Olivia\" — browse more at https://anam.ai/docs/personas/avatars/gallery", "```", "", "The plugin listens to the audio your agent sends to users and publishes a synchronized avatar video as a separate room track — it handles the audio routing itself; do not pipe TTS audio manually.", "", "Both runtimes start the avatar BEFORE the agent session. Starting it later replaces the session's existing audio output.", "", "---", "", "## Python", "", "Install:", "", "```bash", "pip install livekit-agents livekit-plugins-anam", "```", "", "In the agent entrypoint, start the avatar session BEFORE starting the AgentSession, so the avatar is ready the moment the agent first speaks:", "", "```python", "import os", "from livekit.plugins import anam", "", "avatar = anam.AvatarSession(", "    persona_config=anam.PersonaConfig(", "        name=\"Olivia\",", "        avatarId=os.environ[\"ANAM_AVATAR_ID\"],", "    ),", "    api_key=os.environ[\"ANAM_API_KEY\"],", "    api_url=os.environ[\"ANAM_API_URL\"],", ")", "await avatar.start(session, room=ctx.room)", "", "await session.start(", "    agent=agent,", "    room=ctx.room,", ")", "```", "", "---", "", "## Node.js", "", "Install:", "", "```bash", "pnpm add @livekit/agents-plugin-anam", "```", "", "In the agent entrypoint, start the avatar session BEFORE `session.start()`:", "", "```ts", "import * as anam from '@livekit/agents-plugin-anam';", "", "const avatarId = process.env.ANAM_AVATAR_ID;", "if (!avatarId) {", "  console.warn('ANAM_AVATAR_ID is not set — avatar session will not start');", "}", "", "if (avatarId) {", "  const avatarSession = new anam.AvatarSession({", "    personaConfig: {", "      name: 'Olivia',", "      avatarId,", "    },", "    apiUrl: process.env.ANAM_API_URL,", "  });", "  await avatarSession.start(session, ctx.room);", "}", "", "await session.start({ agent, room: ctx.room });", "```", "", "---", "", "## Rules", "", "ALWAYS:", "", "- Keep the existing STT / LLM / TTS pipeline untouched — Anam replaces nothing in it.", "- Start the avatar before `session.start()` in both runtimes.", "- Read `ANAM_API_KEY` from the environment on the server.", "- Keep the agent's instructions voice-friendly: concise replies, no markdown, emojis, asterisks or other symbols — they get spoken aloud.", "", "NEVER:", "", "- Configure a voice, LLM or system prompt on the Anam side — those belong to the LiveKit agent. `PersonaConfig` / `personaConfig` here takes only `name` and `avatarId`.", "- Pipe TTS audio to the avatar manually — the plugin intercepts the room audio itself.", "- Hardcode the API key or import it anywhere that ships to a browser.", "", "## Verify before you declare this done", "", "1. The avatar joins the room as a separate participant and publishes a video track.", "2. The avatar's lips sync to the agent's spoken replies, with no doubled audio.", "3. With `ANAM_AVATAR_ID` unset the agent still runs (voice-only) and logs a clear warning.", "4. `ANAM_API_KEY` appears only in server-side environment config.", "", "## After setup", "", "Swap the face anytime by changing `ANAM_AVATAR_ID` — no code changes. Create a custom avatar from a single photo at https://anam.ai/docs/personas/avatars/custom-avatars. For screen-aware agents (vision), see https://anam.ai/cookbook/gemini-vision-with-anam-livekit.", "", "## Reference", "", "- LiveKit quickstart: https://anam.ai/docs/integrations/livekit/quickstart", "- Node cookbook: https://anam.ai/cookbook/getting-started-with-livekit", "- Python cookbook: https://anam.ai/cookbook/gemini-vision-with-anam-livekit", "- Machine-readable docs index: https://anam.ai/docs/llms.txt", "", "## If anything is unclear", "", "If an API, method, parameter or config field is not shown above or in the docs you fetched, do not invent it. Check https://anam.ai/docs/llms.txt, or stop and ask, rather than guessing.", "", "## Keep these rules", "", "Save the Rules section above into `AGENTS.md` (or your agent's own rules file, e.g. CLAUDE.md / .cursor/rules) so every future session follows them without being told."].join("\n");

export const PrebuiltPrompt = ({name, prompt, promptVersion}) => {
  const copyPrompt = async event => {
    const button = event.currentTarget;
    const label = button.querySelector("[data-copy-label]");
    const initialLabel = "Copy Prompt";
    if (typeof window !== "undefined" && window.posthog && typeof window.posthog.capture === "function") {
      window.posthog.capture("docs_quickstart_prompt_copy_clicked", {
        quickstart: name.toLowerCase(),
        prompt_version: promptVersion,
        prompt_length: prompt.length,
        source_page: window.location.pathname
      });
    }
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(prompt);
      } else {
        const textArea = document.createElement("textarea");
        textArea.value = prompt;
        textArea.setAttribute("readonly", "");
        textArea.style.position = "fixed";
        textArea.style.opacity = "0";
        document.body.appendChild(textArea);
        textArea.select();
        const copied = document.execCommand("copy");
        document.body.removeChild(textArea);
        if (!copied) {
          throw new Error("Copy command failed");
        }
      }
      button.dataset.copied = "true";
      label.textContent = "Copied";
    } catch {
      button.dataset.copied = "false";
      label.textContent = "Copy failed";
    }
    window.setTimeout(() => {
      delete button.dataset.copied;
      label.textContent = initialLabel;
    }, 2000);
  };
  return <aside className="prebuilt-prompt" data-prompt-version={promptVersion} data-prompt-length={prompt.length} aria-label={`${name} agent prompt`}>
      <div className="prebuilt-prompt-copy">
        <p className="prebuilt-prompt-title">
          Build this {name} quickstart with a coding agent
        </p>
        <p className="prebuilt-prompt-description">
          Copy the prompt and paste it into Cursor, Claude Code, Codex, or your
          preferred coding agent.
        </p>
      </div>
      <button className="prebuilt-prompt-button" type="button" onClick={copyPrompt} data-attr="prebuilt-prompt-copy" aria-label={`Copy the pre-built prompt for a ${name} agent`}>
        <span data-copy-label aria-live="polite">
          Copy Prompt
        </span>
      </button>
    </aside>;
};

This quickstart shows how to add an Anam avatar face to a LiveKit voice agent using OpenAI Realtime for the LLM.

<PrebuiltPrompt name="LiveKit" prompt={liveKitAgentPrompt} promptVersion="livekit-agent-prompt-v1" />

## Prerequisites

* [LiveKit CLI](https://docs.livekit.io/home/cli/cli-setup/) installed
* A [LiveKit Cloud](https://cloud.livekit.io) account
* An OpenAI API key
* An Anam API key from [lab.anam.ai](https://lab.anam.ai)

## Set up the agent

Clone the LiveKit Node.js agent starter and install dependencies:

```bash theme={"system"}
git clone https://github.com/livekit-examples/agent-starter-node.git
cd agent-starter-node
pnpm install
```

Download the required model files (VAD and turn detection):

```bash theme={"system"}
pnpm run download-files
```

Install the Anam plugin:

```bash theme={"system"}
pnpm add @livekit/agents-plugin-anam
```

## Configure credentials

Create a `.env.local` file:

```bash .env.local theme={"system"}
# LiveKit Cloud credentials (from cloud.livekit.io)
LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret

# OpenAI (for voice + LLM)
OPENAI_API_KEY=your_openai_key

# Anam (for avatar face)
ANAM_API_KEY=your_anam_key
ANAM_AVATAR_ID=edf6fdcb-acab-44b8-b974-ded72665ee26
```

The avatar ID above is "Mia", one of Anam's stock avatars. Browse others in the [Avatar Gallery](/docs/resources/avatar-gallery) or create your own at [lab.anam.ai/avatars](https://lab.anam.ai/avatars).

## Add the avatar to your agent

Replace the contents of `src/agent.ts`:

```typescript theme={"system"}
import { type JobContext, ServerOptions, cli, defineAgent, voice } from '@livekit/agents';
import * as anam from '@livekit/agents-plugin-anam';
import * as openai from '@livekit/agents-plugin-openai';
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
import dotenv from 'dotenv';
import { fileURLToPath } from 'node:url';

dotenv.config({ path: '.env.local' });

class Assistant extends voice.Agent {
  constructor() {
    super({
      instructions: `You are a helpful voice AI assistant.
You eagerly assist users with their questions.
Your responses are concise, to the point, and without any complex formatting or punctuation including emojis, asterisks, or other symbols.
You are curious, friendly, and have a sense of humor.`,
    });
  }
}

export default defineAgent({
  entry: async (ctx: JobContext) => {
    await ctx.connect();

    // Start the voice session with OpenAI Realtime
    const session = new voice.AgentSession({
      llm: new openai.realtime.RealtimeModel({ voice: 'alloy' }),
    });

    await session.start({
      agent: new Assistant(),
      room: ctx.room,
      inputOptions: {
        noiseCancellation: BackgroundVoiceCancellation(),
      },
    });

    // Start the Anam avatar session
    const avatarId = process.env.ANAM_AVATAR_ID;
    if (!avatarId) {
      console.warn('ANAM_AVATAR_ID is not set. Avatar will not start.');
      return;
    }

    const avatarSession = new anam.AvatarSession({
      personaConfig: {
        name: 'Mia',
        avatarId,
        avatarModel: 'cara-4',
      },
    });

    await avatarSession.start(session, ctx.room);
    console.log('Agent and avatar session started');
  },
});

cli.runApp(new ServerOptions({ agent: fileURLToPath(import.meta.url) }));
```

## Test locally

```bash theme={"system"}
pnpm run dev
```

The agent connects to LiveKit Cloud and waits for rooms. You need a frontend to create a room and connect.

## Set up the frontend

In a new terminal, create the React frontend:

```bash theme={"system"}
lk app create --template agent-starter-react
cd agent-starter-react
pnpm install
```

Create a `.env.local` with your LiveKit credentials:

```bash .env.local theme={"system"}
LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
```

Start the dev server:

```bash theme={"system"}
pnpm dev
```

Open `http://localhost:3000`, click connect, and the avatar appears as the agent speaks.

## Deploy to LiveKit Cloud

```bash theme={"system"}
lk agent deploy --secrets-file=.env.local
```

This uploads your agent code and environment variables. The agent will now automatically join any rooms created in your project.

## Next steps

* [Configuration](/docs/livekit/configuration) — persona config, advanced examples, and API reference
* [Avatar Gallery](/docs/resources/avatar-gallery) — browse stock avatars
* [Create a custom avatar](https://lab.anam.ai/avatars) — use your own face
