About

Pick an AI Avatar SDK by How Much You Can Unbundle — Anam's Three Modes and the Work That Moves to You

What I hit wiring up full, custom_llm and passthrough: a 400, PCM formats, and keeping the API key on the server

When you shop for an AI avatar service, you start by comparing faces. Wiring one up changed my mind. The first thing to check is how much you can unbundle. And the more you unbundle, the heavier your own code gets.

After reading this you'll know which of Anam's three modes to pick and what you have to own afterwards. Also: don't put the API key in the browser.

It started with a 400

Building an Anam tester in app10, I first set only an avatar (avatarId, nothing else). The default WebRTC connection rejects that with 400 avatar_only_requires_integration. To use just the face, you either pick a voice or declare that you'll feed audio yourself (passthrough). That's when the idea of "modes" clicked.

Three modes

STTLLMTTSFacefullAnamAnamAnamAnamcustom_llmAnamyouAnamAnampassthroughyouyouyouAnamThe more stages you take over, the more code lands in your browser and server
Mode personaConfig Anam handles You handle
full voiceId + llmId + systemPrompt STT, LLM, TTS, face Nothing
custom_llm llmId: CUSTOMER_CLIENT_V1 STT, TTS, face Writing replies -> talk() / createTalkMessageStream()
passthrough enableAudioPassthrough: true Face only All of STT, LLM, TTS. PCM via sendAudioChunk()

With custom_llm you listen for MESSAGE_HISTORY_UPDATED and call your own LLM. When the user interrupts, cutting that LLM stream is your job. Anam won't do it for you.

With passthrough you're now in the audio format business. Send PCM s16le, 16kHz, mono as base64 chunks and call endSequence() at the end. On the mic side I also had to write the code that converts the browser's webm/opus into WAV. More work than it sounds.

Which one, when

  • You just need a talking face and Anam's LLM is good enough -> full

  • Replies must come from your data and your prompts (internal doc search, support flows) -> custom_llm. For most teams this is the realistic line

  • The voice has to be yours too (a cloned voice, another TTS) -> passthrough. You take on audio formats, interruptions and sync

I didn't verify in the docs this time whether other avatar services (HeyGen, Tavus and so on) let you swap the LLM or TTS. When you compare, one question is enough: "From which stage can we take over, and what lands on us when we do?"

Keep the API key on the server

Anam's official JavaScript quickstart is a local HTML demo, so it puts the API key in browser script and fetches the session token from there. Fine for a demo. Ship it as is and anyone can read your key.

In app10 I split it like this.

  • The API key lives only on the server

  • The server issues a short-lived session token with the personaConfig baked in. The browser uses that token to open WebRTC and nothing else

  • The server also picks the mode. The browser can't rewrite personaConfig, and an unknown mode falls back to full

I checked that each mode sends the right personaConfig with request specs (WebMock). I haven't connected all three modes with a real key yet. What breaks when you push 24kHz PCM instead of 16kHz into passthrough is something I'll add after seeing it.

If you're integrating an avatar SDK and stuck on choosing a mode or structuring key handling, you can ask negabaro (contact).

Key Concepts

1

Avatar only returns 400: pick a voice or declare passthrough

2

Most teams land on custom_llm: your LLM, and you cut its stream on interruption

3

passthrough takes PCM s16le, 16kHz, mono as base64 chunks, ended with endSequence()

4

API key on the server, only a session token in the browser; the server picks the mode

Use Cases

A reception avatar that answers from internal docs: custom_llm An avatar speaking in a cloned voice: passthrough
← A Talking AI Avatar Is Not One Model — It Is STT, LLM and TTS With a Face on the End, and Latency Adds Up FaceFusion Is a Parts Assembler, Not a Method — A Map of Four Families of Face-Changing AI →