ElevenLabs Agents와 Cloudflare Workers로 독일어 회화 파트너 만들기
요약
ElevenLabs agents와 Cloudflare Workers를 활용하여 원어민 수준의 독일어 회화 연습 파트너를 구현한 사례입니다. 이 에이전트는 사용자와 자연스러운 대화를 나누고, 문법적 오류나 어휘 선택 실수를 교정하며, 이전 세션 내용을 기억하는 등 실제 학습 경험을 제공합니다.
핵심 포인트
- ElevenLabs agents와 Cloudflare Workers 조합으로 AI 음성 파트너 구현
- 실시간 대화 중 문법/어휘 오류를 지속적으로 교정 및 피드백 제공
- 세션별 실수와 신규 어휘를 저장하고 리뷰할 수 있는 메모리 기능 탑재
외국어를 배운 사람이라면 누구나 시험에 합격하는 것과 원어민과의 실제 유창함 사이의 간극을 알고 있습니다. 이 간극을 메우려면 원어민 수준의 사람과의 연습이 필요한데, 모든 사람이 원어민 파트너를 갖거나 끝없는 수업을 들을 예산과 시간을 가진 것은 아닙니다.
그래서 저는 스스로에게 질문했습니다. 만약 AI 음성 에이전트가 그 파트너가 될 수 있다면 어떨까? 원어민 수준으로 편하게 대화할 상대, 인내심을 잃지 않으며 중요한 실수가 있을 때 지적해 주는 그런 존재 말입니다.
저는 이를 ElevenLabs agents와 Cloudflare Workers를 사용해 만들었습니다.
기능 설명
- 친구와 채팅하듯 독일어로 편안한 대화를 나눕니다.
- 실제 문법 및 단어 선택 실수를 교정하고, 매번 실수할 때마다 멈추는 대신 대화를 계속 이어갑니다.
- 각 통화가 끝난 후 발생한 실수와 새로운 어휘를 검토 페이지에 저장하며, 여기서 학습 항목을 삭제할 수 있습니다.
- 이전 세션 내용을 기억하므로 매번 처음부터 시작하지 않습니다.
- 사용자의 이름을 불러주고 개인적인 분위기를 유지합니다.
작동 방식
ElevenLabs agent가 음성 처리를 담당하고, Cloudflare Worker (Hono)가 나머지 모든 것을 처리합니다. 즉, 작은 Vite + TypeScript 페이지를 제공하고, D1에 데이터를 저장하며, 에이전트가 호출하는 엔드포인트를 노출합니다.
- 브라우저가 페이지를 요청합니다. Worker는 사용자를 인증하고 HTML을 재작성한 후 전송합니다.
- 위젯은 동적 변수로 서명된 사용자 ID와 함께 통화를 시작합니다.
- 통화 중 에이전트는 제 Worker에 있는 두 가지 도구, 즉 최근 세션에서 발생한 내용을 반환하는
recall과 대화 내용의 항목을 저장하는store를 호출합니다.
- 통화가 끝나면 ElevenLabs에서 post-call webhook을 전송합니다. Worker는 이를 검증하고 실수와 어휘를 D1에 기록합니다.
- 리뷰 페이지는 해당 테이블들을 읽어옵니다.
설명할 가치가 있는 결정 사항들
메모리(Memory). 제가 이것을 구축했을 때, ElevenLabs agent에는 세션 간에 내장된 메모리가 없었기 때문에 직접 추가했습니다. webhook이 각 세션을 저장하고, agent는 통화가 시작될 때 recall을 호출합니다. 저는 최근 두세 개의 세션만 불러옵니다. 더 많은 것을 가져오면 agent의 목소리가 로봇처럼 들립니다. 아무도 친구와의 수백 번의 이전 대화를 기억하지 못합니다.
브라우저에 비밀 정보가 없음(No secrets in the browser). Worker는 사용자 ID를 HMAC-서명된 ID로 변환하여 브라우저에 전달하기 전에 페이지에 넣습니다. 서명 키는 Worker에만 존재합니다.
const rewritten = new HTMLRewriter()
.on('elevenlabs-convai', {
element(el) {
...
no-store 헤더가 중요한데, 이 페이지는 개인화되어 있어 공유 캐시에서 절대 제공되어서는 안 되기 때문입니다.
agent의 엔드포인트는 공개되지 않습니다(The agent's endpoints are not public). agent는 어떤 데이터에 접근하는지 결코 스스로 결정하지 않습니다. 위젯은 서명된 사용자 ID를 동적 변수로 agent에게 전달하고, 모든 도구 호출은 이를 다시 전송합니다. 도구 라우트는 읽거나 쓰기 전에 서명을 검증하므로, 조작되거나 변경된 ID는 거부됩니다. post-call webhook도 자체적인 검사를 수행합니다. 라우트 연결을 단순화하면 다음과 같습니다:
app.get('/tools/recall', recallHandler); // signed user_id를 먼저 검증함
app.post('/tools/store', storeHandler); // 쓰기 전에 동일한 검사
app.post('/api/webhook', saveSession); // webhook 서명 검사
브라우저는 서명된 ID를 볼 수 있지만, 새로운 ID를 생성할 수는 없습니다.
라우팅(Routing). 정적 파일은 에셋 번들에서 바로 가져옵니다. /, /api/*, 그리고 /tools/*만 Worker를 호출합니다.
다음으로 할 일
- 일반 웹훅(webhook) 도구를 MCP 도구로 교체하여 더 깔끔하고 표준화된 도구 접근 방식을 구현합니다.
- 임시 사용자(throwaway user), 호출 길이 제한 및 일일 지출 한도를 갖는 데모 모드를 추가하여 크레딧을 소모하지 않고도 공개적으로 사용할 수 있게 합니다.
- 시간이 지남에 따라 어떤 오류 카테고리가 줄어드는지 등 진행 상황을 보여줍니다.
다음으로 할 작업
- 일반 웹훅 도구를 MCP 도구로 교체하여 더 깔끔하고 표준화된 도구 접근 방식을 구현합니다.
- 서명된 사용자 ID에 만료 기한을 추가하여 복사된 ID가 시간이 지난 후 작동하지 않도록 합니다.
- 임시 사용자(throwaway user), 호출 길이 제한 및 일일 지출 한도를 갖는 데모 모드를 추가하여 크레딧을 소모하지 않고도 공개적으로 사용할 수 있게 합니다.
- 시간이 지남에 따라 어떤 오류 카테고리가 줄어드는지 등 진행 상황을 보여줍니다.
음성 에이전트를 구축하고 제 에이전에 대해 더 알고 싶다면 댓글을 남겨주세요. 자세한 내용을 기꺼이 공유해 드리겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
