AI 아바타 SDK 는 「어디까지 떼어 낼 수 있나」 로 고른다 — Anam 3모드와 떼어 낼수록 늘어나는 책임
full · custom_llm · passthrough 를 붙여 보며 걸린 것 — 400 에러, PCM 형식, 그리고 API 키는 서버에
AI 아바타 서비스를 고를 때 얼굴 품질부터 비교하게 된다. 직접 붙여 보고 생각이 바뀌었다. 먼저 볼 건 어디까지 떼어 낼 수 있나다. 그리고 떼어 낼수록 우리 쪽 코드가 무거워진다.
이 글을 읽으면 Anam 의 세 모드 중 무엇을 고를지, 고른 뒤 무엇을 직접 챙겨야 하는지 정리된다. 하나 더. API 키는 브라우저에 두지 않는다.
시작은 400 에러였다
app10 에 Anam 테스터를 만들면서 처음엔 아바타만 지정해 봤다(avatarId 만). 기본 WebRTC 연결에서 400 avatar_only_requires_integration 으로 거절된다. 얼굴만 쓰려면 목소리를 정하거나, 오디오를 직접 넣겠다고(passthrough) 밝혀야 한다. 여기서 「모드」 라는 게 보이기 시작했다.
세 모드
| 모드 | personaConfig | Anam 이 맡는 것 | 우리가 맡는 것 |
|---|---|---|---|
| full | voiceId + llmId + systemPrompt |
STT · LLM · TTS · 얼굴 | 없음 |
| custom_llm | llmId: CUSTOMER_CLIENT_V1 |
STT · TTS · 얼굴 | 대답 생성 → talk() / createTalkMessageStream() |
| passthrough | enableAudioPassthrough: true |
얼굴만 | STT · LLM · TTS 전부. PCM 을 sendAudioChunk() 로 |
custom_llm 은 MESSAGE_HISTORY_UPDATED 를 듣고 우리 LLM 을 부른다. 사용자가 끼어들면 그 LLM 스트림도 우리가 끊어야 한다. Anam 이 대신 끊어 주지 않는다.
passthrough 는 오디오 형식을 맞추는 일이 생긴다. PCM s16le · 16kHz · mono 를 base64 조각으로 보내고 endSequence() 로 끝을 알린다. 마이크 쪽도 브라우저가 주는 webm/opus 를 WAV 로 바꾸는 코드를 직접 썼다. 생각보다 일이다.
그래서 언제 무엇을
대화하는 얼굴이 일단 필요하고, 대답은 Anam 쪽 LLM 으로 충분하다 → full
우리 데이터 · 우리 프롬프트로 대답해야 한다(사내 문서 검색, 상담 흐름) → custom_llm. 대부분은 여기가 현실적인 선이다
목소리까지 우리 것이어야 한다(클로닝한 목소리, 다른 TTS) → passthrough. 오디오 형식 · 끼어들기 · 동기화를 전부 떠안는다
다른 아바타 서비스(HeyGen, Tavus 등)도 LLM · TTS 교체를 지원하는지는 이번에 문서로 확인하지 못했다. 비교할 때는 같은 질문 하나면 된다. 「어느 단계부터 우리가 맡을 수 있고, 그때 무엇이 우리 쪽으로 넘어오나」.
API 키는 서버에
Anam 공식 JavaScript quickstart 는 로컬 HTML 데모라서 API 키를 브라우저 스크립트에 넣고 세션 토큰을 받아 온다. 데모로는 편하다. 그대로 배포하면 키가 누구에게나 보인다.
app10 은 이렇게 나눴다.
API 키는 서버에만 둔다
서버가 personaConfig 를 담아 잠깐 쓰는 세션 토큰을 발급한다. 브라우저는 그 토큰으로 WebRTC 만 연다
모드도 서버가 정한다. 브라우저가 personaConfig 를 마음대로 못 바꾼다. 모르는 mode 가 오면 full 로 떨어뜨린다
모드별로 personaConfig 가 맞게 나가는지는 request spec(WebMock)으로 확인했다. 실제 키로 세 모드를 다 연결해 보는 건 아직이다. passthrough 에 16kHz 가 아닌 24kHz PCM 을 넣으면 어떻게 깨지는지는 직접 보고 나서 덧붙이겠다.
아바타 SDK 를 붙이면서 모드 선택이나 키 관리 구조가 고민이라면 negabaro 에 물어봐도 된다 (문의).
핵심 개념
아바타만 지정하면 400 — 목소리를 정하거나 passthrough 를 밝힌다
대부분은 custom_llm — 우리 LLM 을 쓰고 끼어들기 때 스트림을 직접 끊는다
passthrough 는 PCM s16le · 16kHz · mono 를 base64 조각으로, 끝은 endSequence()
API 키는 서버에, 브라우저엔 세션 토큰만. 모드도 서버가 정한다