Agora 실시간 Agent STT + 번역 구현하기
한국어로 말하는데 화면에는 곧바로 영어 자막이 뜨게 하려면 어떻게 해야 할까요? 클라이언트가 직접 음성을 인식하는 게 아니라, Agora ConvoAI 에이전트가 클라우드에서 RTC 채널에 들어와 음성을 듣고 인식과 번역 결과를 DataStream으로 돌려보내는 구조입니다. 이 글은 UDP 크기 제한 때문에 잘게 쪼개져 오는 청크를 어떻게 재조립하고, turn_id 기반 카드와 세그먼트, 1초 디바운스로 연속 발화를 자연스럽게 묶어 화면에 보여주는지까지 차근차근 짚어줍니다.
Agora ConvoAI를 활용하면 RTC 채널의 음성을 실시간으로 텍스트 변환(STT) 하고 다른 언어로 번역까지 할 수 있습니다. 이 글에서는 ConvoAI의 STT + Translation이 어떻게 동작하는지, DataStream 청크 프로토콜과 카드 시스템의 구조를 정리합니다.
전체 데이터 흐름
클라이언트가 직접 음성 인식을 하는 게 아닙니다. 클라우드 AI 에이전트가 RTC 채널에 참여하여 음성을 듣고, 인식·번역 결과를 DataStream으로 돌려보내는 구조입니다.
핵심 구성 요소
| 구성 요소 | 역할 |
|---|---|
/api/token | RTC 토큰 생성 (agora-token 패키지) |
/api/convoai | STT REST API 프록시 (credentials 보호) |
page.tsx | 클라이언트 — RTC 연결, 미디어, STT, UI |
UID 구성
| UID | 용도 |
|---|---|
| Random 1000~100999 | 로컬 사용자 |
88888 | ConvoAI STT Agent |
55555 | 화면 공유 (별도 RTC client) |
STT Agent 시작/중지
STT 시작 — Agent Join
v2vt_base 프리셋: Voice-to-Voice Translation의 기본 설정. TTS를 끄면 STT + 번역 텍스트만 받을 수 있습니다.
STT 중지 — Agent Leave
DataStream 청크 프로토콜
ConvoAI는 JSON 메시지를 base64 인코딩 후 여러 패킷으로 분할하여 전송합니다. Agora 데이터 스트림(sendStreamMessage)의 한도는 패킷당 최대 1KB(1024B), 채널당 초당 최대 30패킷·6KB입니다. 1KB를 초과하는 JSON 메시지는 base64 인코딩 후 여러 패킷으로 분할 전송됩니다.
청크 포맷
| 필드 | 타입 | 설명 |
|---|---|---|
msgId | string | 메시지 고유 ID |
partIdx | number | 파트 인덱스 (1-based) |
partSum | number | "???" | 총 파트 수 (미확정이면 "???") |
base64Data | string | base64 인코딩된 JSON 조각 |
3파트 메시지 예시
재조립 과정
주의:
partIdx는 1-based입니다. 0-based 가정으로 재조립하면undefined가 섞여 JSON 파싱이 실패합니다.
메시지 타입
user.transcription — 음성 인식 결과
- 같은
turn_id로 여러 번 전송 (partial → final) text는 누적:"안녕"→"안녕하세요"→"안녕하세요"(final)
user.translation — 번역 결과
transcript_text: 원본 음성 인식 텍스트 (최종)text: 번역된 텍스트
카드 & 세그먼트 시스템
실시간 음성을 UI에 표시하려면 "누가 언제 뭐라고 했는지"를 구조화해야 합니다. 이 데모는 칠판 시스템으로 이를 해결합니다.
구조
| 개념 | 비유 | 역할 |
|---|---|---|
| 카드 | 칠판의 칸 | 한 사용자의 연속 발화를 하나로 묶음 |
| 세그먼트 | 칸 안의 각 줄 | 각 turn_id별 텍스트 저장 |
| 타이머 | 1초 알람 | 1초 동안 조용하면 칸을 노트로 옮김 |
| clearTimeout | 알람 취소 | 새 음성이 오면 알람 취소 → 같은 칸 유지 |
turn_id란?
ConvoAI가 "말이 끊겼다"고 판단할 때마다 숫자가 올라갑니다.
같은 문장 = 같은 turn_id (텍스트가 점진적으로 확장):
새 문장 = 새 turn_id:
왜 세그먼트가 필요한가?
세그먼트 없이 텍스트를 하나의 변수에 저장하면:
세그먼트를 쓰면:
묶이는 원리 — 1초 디바운스
연속된 발화가 하나의 카드로 묶이는 핵심 메커니즘:
실제 시나리오: "안녕하세요" → (0.3초 쉼) → "반갑습니다"
| 단계 | 이벤트 | 칠판 상태 | 타이머 |
|---|---|---|---|
| 1 | transcription (turn_id=1, "안녕하세요") | 새 카드 생성, 세그먼트 추가 | 없음 |
| 2 | translation (turn_id=1, "Hello", final) | 번역 추가 | 1초 알람 시작 |
| 3 | 0.3초 후 transcription (turn_id=2, "반갑습니다") | clearTimeout → 같은 카드에 세그먼트 추가 | 알람 취소됨 |
| 4 | translation (turn_id=2, "Nice to meet you", final) | 번역 추가 | 1초 알람 다시 시작 |
| 5 | 1초간 조용 | 카드를 노트로 이동 | 발동 → 완료 |
화면 공유 구조
Agora SDK는 하나의 RTC client에서 2개의 video track을 publish할 수 없습니다. 화면 공유를 위해 별도 RTC client(UID 55555)를 생성합니다.
브라우저의 "공유 중지" 버튼 클릭 시 track-ended 이벤트로 자동 정리됩니다.
지원 언어
| 코드 | 언어 |
|---|---|
ko-KR | 한국어 |
en-US | English |
zh-CN | 中文 |
ja-JP | 日本語 |
es-ES | Español |
fr-FR | Français |
de-DE | Deutsch |
음성 인식 언어와 번역 대상 언어를 각각 선택할 수 있습니다. 예를 들어 한국어로 말하면서 영어 번역을 실시간으로 받을 수 있습니다.
[NEEDS VERIFICATION] Agora 공식 문서 기준 실시간 번역(Real-time Translation)은 Beta 단계로 분류되며, 지원 언어 목록과 SLA가 수시로 갱신됩니다. 위 코드 예시의 프리셋 이름(
v2vt_base)과 DataStream 청크 헤더 포맷(msgId|partIdx|partSum|base64)은 실제 구현/SDK 버전에 종속적인 값이므로, 배포 전 사용 중인 ConvoAI Engine 버전의 레퍼런스로 재확인하세요.
핵심 요약
- STT Agent가 클라우드에서 음성 인식 + 번역을 수행하고, DataStream으로 결과 전송
- DataStream은 UDP 청크로 분할 전송되며, 클라이언트에서 재조립 후 JSON 파싱
- 카드 시스템이 사용자별 발화를 그룹화하고, 1초 디바운스로 연속 발화를 묶음
- 세그먼트가 turn_id별 텍스트를 보존하여 덮어쓰기 방지
- 화면 공유는 별도 RTC client로 구현 (SDK 제약 때문)
관련 글
- #5 Conversational AI 통합 가이드 — 이 글의 STT/번역 에이전트가 올라타는 ConvoAI 파이프라인의 전체 구조
- #3 RTM 실시간 채팅 시스템 구축 — DataStream 대신 RTM으로 transcription을 받는 권장 경로(보장 전송)
- #23 오디오 파이프라인 해부 — Opus/RTP/Jitter Buffer — STT 에이전트가 채널에서 받아 ASR에 먹이는 오디오가 어떻게 운반되는지
- #19 Base64 & 바이너리 기초 — DataStream 청크가 JSON을 base64로 인코딩하는 이유와 디코딩 원리
- #2 Agora RTC 실시간 화상통화 구현 — STT를 얹기 전, RTC 채널/토큰/트랙 연결의 베이스라인
참고 자료
- Conversational AI — Display live transcripts — transcription/translation 메시지를 클라이언트에서 수신·렌더링하는 공식 가이드
- Conversational AI — Start a conversational AI agent (REST) — agent join REST API와
properties(asr/translation/tts/preset) 파라미터 정의 - Real-Time Speech-To-Text — Parse transcription data — DataStream으로 들어오는 STT 메시지(protobuf/JSON) 구조와 파싱 절차
- Real-Time Speech-To-Text — Real-time translation (Beta) — 번역 기능의 Beta 상태·지원 언어·설정 방법
- AgoraRTC.createScreenVideoTrack — Web SDK API Reference — 화면 공유 트랙 생성 및 별도 client publish 제약 확인
- Conversational AI Engine — Release notes — 프리셋/번역 기능의 버전별 변경 이력(배포 전 버전 확인용)
실제 데모
/agora-demo/stt