EROKE ORIGINAL WEBBOOK

말로 일하는 AI

말로 일하는 AI 웹북 표지
전체 목차15개 장
  1. 말로 일하는 AI: 1장 음성은 채팅에 소리를 붙인 것이 아니다
  2. 말로 일하는 AI: 2장 실시간 음성과 단계형 파이프라인을 선택한다
  3. 말로 일하는 AI: 3장 대화의 목적과 중단 조건을 먼저 쓴다
  4. 말로 일하는 AI: 4장 마이크 권한과 첫 연결 경험을 설계한다
  5. 말로 일하는 AI: 5장 듣기·말하기·침묵을 화면으로 보여 준다
  6. 말로 일하는 AI: 6장 전사문과 대화 상태를 분리한다
  7. 말로 일하는 AI: 7장 도구 계약을 작고 결정적으로 만든다
  8. 말로 일하는 AI: 8장 예약은 보류와 확인으로 나눈다
  9. 말로 일하는 AI: 9장 잘못 들은 이름·날짜·숫자를 복구한다
  10. 말로 일하는 AI: 10장 끼어들기와 중단을 존중한다
  11. 말로 일하는 AI: 11장 지연·끊김·재연결을 다룬다
  12. 말로 일하는 AI: 12장 음성 개인정보와 보존 기간을 제한한다
  13. 말로 일하는 AI: 13장 WebRTC Realtime 세션을 연결한다
  14. 말로 일하는 AI: 14장 음성 평가 세트를 만들고 회귀를 잡는다
  15. 말로 일하는 AI: 15장 운영 가능한 음성 서비스를 배포한다

실시간 음성 에이전트를 안전하게 만드는 첫 프로젝트

CallGarden 상담 접수 도우미로 대화 상태, 명시적 확인, 취소, 개인정보, WebRTC Realtime 연결과 음성 회귀 평가를 단계적으로 익힌다.

상태: 출간 후보 웹교정쇄 · 음성·법률 현장 검수 대기 · 15개 장

음성 흐름을 확인과 사람 검토로 연결하는 표지 일러스트
음성 흐름을 확인과 사람 검토로 연결하는 표지 일러스트

음성 에이전트의 데모는 쉽다. 마이크를 켜고 모델이 자연스럽게 대답하면 놀랍다. 하지만 실제 서비스는 다른 질문을 요구한다. 사용자가 말을 끊으면 멈추는가? 이름과 날짜를 잘못 들었을 때 확인하는가? 침묵이 길면 어떻게 복구하는가? 예약을 했다고 말했지만 실제 시스템에는 기록이 없다면 누가 책임지는가?

이 책은 “사람처럼 말하는 AI”보다 틀렸을 때 멈추고 확인할 줄 아는 음성 서비스를 만든다. 실습 프로젝트 CallGarden은 식물 관리 상담을 접수한다. 처음에는 마이크와 네트워크 없이 텍스트 turn을 음성 대화처럼 재생한다. 도메인 규칙이 안정된 뒤 브라우저 마이크와 WebRTC 실시간 세션을 선택적으로 연결한다. 기본 실습에서는 실제 전화나 예약을 만들지 않는다.

  1. 음성은 채팅에 소리를 붙인 것이 아니다
  2. 실시간 음성과 단계형 파이프라인을 선택한다
  3. 대화의 목적과 중단 조건을 먼저 쓴다
  4. 마이크 권한과 첫 연결 경험을 설계한다
  5. 듣기·말하기·침묵을 화면으로 보여 준다
  6. 전사문과 대화 상태를 분리한다
  7. 도구 계약을 작고 결정적으로 만든다
  8. 예약은 보류와 확인으로 나눈다
  9. 잘못 들은 이름·날짜·숫자를 복구한다
  10. 끼어들기와 중단을 존중한다
  11. 지연·끊김·재연결을 다룬다
  12. 음성 개인정보와 보존 기간을 제한한다
  13. WebRTC Realtime 세션을 연결한다
  14. 음성 평가 세트를 만들고 회귀를 잡는다
  15. 운영 가능한 음성 서비스를 배포한다
말하기에서 전사, 상태, 확인, 사람 검토로 이어지는 CallGarden 흐름
말하기에서 전사, 상태, 확인, 사람 검토로 이어지는 CallGarden 흐름

1장 음성은 채팅에 소리를 붙인 것이 아니다

텍스트에서는 사용자가 답을 다시 읽을 수 있다. 음성은 지나간다. 주변 소음, 억양, 마이크 품질, 연결 지연이 의미를 바꾼다. 사용자가 망설이는 침묵과 통신이 끊긴 침묵도 소리만으로 구분하기 어렵다.

따라서 음성 UI는 네 가지 상태를 분명히 보여 줘야 한다.

  • 지금 듣고 있음
  • 이해한 내용을 확인 중
  • AI가 말하고 있음
  • 연결 또는 도구 실행에 문제가 있음

CallGarden의 목표는 식물 상담 예약을 확정하는 것이 아니다. 서비스 종류, 희망 날짜, 이름을 모아 접수 초안을 만들고 담당자가 확인하도록 넘기는 것이다. 이 경계 덕분에 잘못 들은 한 단어가 실제 약속이 되는 위험을 줄인다.

첫 실습은 마이크를 쓰지 않는다. 다음 turn을 코드로 넣는다.


applyTurn(session,{
  service:'식물 진단',
  date:'2026-08-10',
  name:'이하늘',
  intent:'provide'
});

결과는 awaiting_confirmation이다. 이름과 날짜가 모두 있어도 명시적 확인 전에는 완료하지 않는다. 음성 모델을 붙이기 전에 이 규칙이 테스트로 고정되어야 한다.

↑ 목차로 돌아가기

2장 실시간 음성과 단계형 파이프라인을 선택한다

음성 에이전트에는 두 가지 대표 구조가 있다.

구조 흐름 잘 맞는 경우
실시간 speech-to-speech 음성을 모델이 직접 듣고 말함 자연스러운 대화, 낮은 지연, 끼어들기
단계형 음성→텍스트→업무 로직→텍스트→음성 기록·승인·결정 규칙을 명확히 보고 싶을 때

OpenAI의 현재 공식 안내도 자연스러운 저지연 대화에는 실시간 세션을, 중간 텍스트 제어와 승인 중심 흐름에는 단계형 구성을 고려하도록 설명한다. 이 책은 단계형 사고로 도메인 규칙을 먼저 만들고, 13장에서 실시간 transport를 붙인다.

구조 선택 질문은 다음과 같다.

  • 사용자가 답을 끊고 바로 방향을 바꿔야 하는가.
  • 전사문을 검토하거나 저장해야 하는가.
  • 응답 전 정책 검사와 사람 승인이 필요한가.
  • 전화망 연결이 필요한가, 브라우저 안에서 충분한가.
  • 실패했을 때 텍스트 채널로 전환할 수 있는가.

한 서비스 안에서 두 구조를 섞을 수도 있다. 인사와 탐색은 실시간으로 하고, 결제·예약 확정은 화면에 요약을 보여 준 뒤 단계형 승인으로 넘긴다.

↑ 목차로 돌아가기

3장 대화의 목적과 중단 조건을 먼저 쓴다

“친절한 예약 상담원”은 역할 설명일 뿐 완료 조건이 아니다. 대화 계약에는 수집할 필드, 금지 행동, 확인 규칙, 사람에게 넘길 조건이 필요하다.


목적: 상담 접수 초안을 만든다.
필수: 서비스 종류, YYYY-MM-DD 날짜, 예약자 이름.
금지: 실제 예약 확정, 가격 보장, 의료적 식물 독성 단정.
확인: 세 필드를 짧게 다시 읽고 사용자의 명시적 동의를 받는다.
중단: 사용자가 취소·그만을 말하면 즉시 멈춘다.
인계: 날짜가 모호하거나 세 번 이해하지 못하면 사람 연결을 제안한다.

한 turn에 질문을 하나만 한다. “성함과 날짜와 서비스와 전화번호를 말씀해 주세요”는 기억 부담이 크다. 이미 들은 정보를 다시 묻지 않되, 중요한 숫자와 고유명사는 확인한다.

↑ 목차로 돌아가기

4장 마이크 권한과 첫 연결 경험을 설계한다

페이지를 열자마자 마이크 권한을 요청하지 않는다. 사용자가 서비스의 목적, 녹음 여부, 중단 방법을 본 뒤 “대화 시작”을 눌렀을 때 요청한다.

권한이 거부되면 오류로 몰지 않는다. 다음 선택지를 준다.

  • 브라우저 주소창에서 권한을 다시 허용하는 방법
  • 마이크 없이 텍스트로 연습하기
  • 전화가 아닌 일반 예약 양식 사용하기

HTTPS가 아닌 환경에서는 브라우저 마이크가 제한될 수 있다. 로컬 개발의 localhost 예외와 실제 배포의 HTTPS 요구를 구분한다. 권한 상태를 서버가 추측하지 않고 브라우저의 실제 결과로 표시한다.

↑ 목차로 돌아가기

5장 듣기·말하기·침묵을 화면으로 보여 준다

음성만 제공하면 청각 장애 사용자, 소리 내기 어려운 환경, 발음을 확인하려는 사용자에게 불리하다. 화면에는 현재 상태, 마지막 전사, 수정 버튼, 중단 버튼을 둔다.


[듣는 중] 사용자의 말을 기다리고 있습니다.
들은 내용: “8월 10일 식물 진단”
[내용 수정] [대화 중단]

AI가 말할 때도 자막을 제공한다. 자막은 오디오가 시작되기 전에 전체 답을 먼저 노출해 대화 타이밍을 깨뜨리지 않도록 동기화 방식을 검토한다. 애니메이션 파형만으로 상태를 전달하지 않고 텍스트 레이블을 함께 쓴다.

침묵 타이머는 압박하지 않는다. 짧은 침묵에는 기다리고, 중간 침묵에는 “천천히 말씀하셔도 됩니다”라고 알리며, 긴 침묵에는 연결 상태를 확인하고 텍스트 전환을 제안한다.

↑ 목차로 돌아가기

6장 전사문과 대화 상태를 분리한다

전사문은 관찰 데이터이고, 대화 상태는 업무 판단이다. “다음 주 화요일”이라는 전사문을 바로 2026-08-11로 저장하면 기준 날짜와 시간대를 잃는다.


{
  "transcript": "다음 주 화요일 오후요",
  "candidate": {"date":"2026-08-11","period":"afternoon"},
  "confidence": "needs_confirmation",
  "timezone": "Asia/Seoul"
}

원문, 해석 후보, 확인 상태를 구분하면 사용자가 수정할 수 있다. 모델이 새 turn에서 다른 날짜를 제시하면 조용히 덮어쓰지 않고 변경을 확인한다.

세션 상태는 작게 유지한다.


collecting → awaiting_confirmation → completed
          ↘ cancelled
          ↘ handoff_requested

모델의 자유로운 문장과 상태 전이를 분리한다. 허용된 이벤트만 도메인 함수에 넘기고, 함수가 다음 상태를 결정한다.

CallGarden의 대화 전사와 결정적 확인 카드를 나란히 보여 주는 실습 화면
CallGarden의 대화 전사와 결정적 확인 카드를 나란히 보여 주는 실습 화면

왼쪽 대화는 자연어이고 오른쪽 카드는 업무 상태다. 사용자가 “네, 맞아요”를 선택하기 전 상태가 awaiting_confirmation인지, 실제 예약이 아니라는 경고가 보이는지 함께 확인한다.

↑ 목차로 돌아가기

7장 도구 계약을 작고 결정적으로 만든다

음성 에이전트가 호출하는 도구는 한 가지 일을 해야 한다. manage_booking 하나에 검색, 생성, 취소, 결제를 모두 넣지 않는다.


list_available_dates(service, month)
create_booking_draft(service, date, name, request_id)
request_human_review(draft_id, reason)
cancel_draft(draft_id)

각 도구에는 허용 값, 필수 필드, 오류 코드, 외부 행동 여부를 쓴다. 읽기 도구와 쓰기 도구를 구분한다. 쓰기 도구는 확인된 필드와 멱등성 키를 요구한다.

도구 결과를 그대로 길게 읽지 않는다. 사용자에게 필요한 값만 자연어로 요약하고, 중요한 날짜와 비용은 화면에도 표시한다. 도구가 실패하면 성공한 것처럼 이어 가지 않는다.

↑ 목차로 돌아가기

8장 예약은 보류와 확인으로 나눈다

음성 인식은 확률적이고 예약은 약속이다. 두 성격을 한 단계에 묶지 않는다.


대화에서 후보 수집
→ 화면 요약
→ 사용자 확인
→ 접수 초안 생성
→ 담당자 확인
→ 실제 예약 확정 알림

CallGarden의 draft_created 이벤트는 외부 예약이 아니다. 이 구분을 응답 문구에도 유지한다.


나쁨: 예약이 완료되었습니다.
좋음: 접수 초안을 만들었습니다. 담당자가 확인한 뒤 예약 가능 여부를 안내합니다.

사용자가 같은 확인을 두 번 말하거나 연결이 재시도되어도 요청 ID로 초안 하나만 만든다. 날짜가 지나거나 서비스 정책이 바뀌면 이전 확인을 재사용하지 않는다.

↑ 목차로 돌아가기

9장 잘못 들은 이름·날짜·숫자를 복구한다

중요한 엔터티는 짧게 되읽는다. 모든 문장을 반복하면 대화가 느려지므로 위험 기반으로 선택한다.

  • 이름: 화면 표기와 음성 되읽기
  • 날짜: 요일을 함께 말해 교차 확인
  • 전화번호: 필요한 경우 끝 네 자리 단위로 확인
  • 금액: 통화 단위와 총액을 함께 표시

후보가 둘이면 억지로 하나를 선택하지 않는다.


“민지”와 “민주” 중 어느 이름인가요?
8월 10일 월요일과 8월 11일 화요일 중 어느 날짜인가요?

세 번 이해하지 못하면 같은 질문을 더 크게 반복하지 않는다. 텍스트 입력이나 사람 연결을 제안한다. 발음이 다르다는 이유로 사용자를 탓하는 문구를 피한다.

↑ 목차로 돌아가기

10장 끼어들기와 중단을 존중한다

사용자가 “잠깐”, “아니요”, “그만”이라고 말하면 현재 음성 출력을 멈춰야 한다. 끼어들기는 대화 품질 기능이면서 안전 기능이다.

중단 뒤에는 마지막으로 확정된 상태를 기준으로 짧게 묻는다.


“알겠습니다. 날짜 설명을 멈췄어요. 서비스 종류부터 바꿀까요?”

오디오 출력 취소와 도구 실행 취소는 다르다. 말을 멈췄다고 이미 시작된 외부 요청이 자동으로 취소되지는 않는다. 쓰기 도구를 호출하기 전 확인 게이트를 두는 이유다. 취소 가능한 작업에는 취소 토큰과 상태를 설계한다.

↑ 목차로 돌아가기

11장 지연·끊김·재연결을 다룬다

음성에서 1초는 길다. 그렇다고 지연을 숨기려고 근거 없는 채움말을 계속 말하면 신뢰가 떨어진다. 상태별 지연 예산을 측정한다.

  • 마이크 권한부터 연결 완료
  • 발화 종료부터 첫 응답 오디오
  • 도구 호출 시작부터 결과
  • 끼어들기부터 출력 중단

도구가 오래 걸리면 “예약 가능 시간을 확인하고 있어요”처럼 실제 작업을 설명한다. 제한 시간을 넘으면 사용자가 기다릴지, 텍스트 알림을 받을지 선택하게 한다.

재연결 때 이전 세션을 무조건 이어 붙이지 않는다. 사용자에게 마지막 확인 내용을 보여 주고 계속할지 묻는다. 세션 ID와 요청 ID를 구분해 중복 초안을 막는다.

↑ 목차로 돌아가기

12장 음성 개인정보와 보존 기간을 제한한다

음성에는 이름뿐 아니라 주변 사람의 대화, 건강 정보, 주소가 우연히 들어갈 수 있다. 필요한 정보만 요청하고 보존 기간을 먼저 정한다.


원음: 기본 저장 안 함
부분 전사: 세션 중 수정용, 종료 뒤 삭제
접수 초안: 서비스·날짜·이름만, 담당자 처리 기간 동안 보존
운영 로그: 비식별 세션 ID와 오류 코드

실제 보존 정책은 서비스 목적과 법률 검토에 따라 달라진다. 녹음한다면 시작 전에 목적, 보존 기간, 거부 방법을 명확히 고지한다. 민감 정보가 전사되면 로그에서 제거한다. 개발 중 실제 고객 음성을 테스트 fixture로 복사하지 않는다.

↑ 목차로 돌아가기

13장 WebRTC Realtime 세션을 연결한다

브라우저 기반 실시간 음성에는 WebRTC가 권장된다. 표준 API 키를 브라우저에 넣지 않는다. 현재 공식 문서가 설명하는 방식은 개발자 서버가 세션 초기화 요청을 대신 보내거나 단기 자격을 만들어 브라우저가 연결하도록 하는 것이다.

이 책의 예제는 통합 인터페이스를 사용한다.


브라우저: 마이크 → RTCPeerConnection → SDP offer
우리 서버: 세션 설정 + SDP → OpenAI /v1/realtime/calls
OpenAI: SDP answer → 우리 서버 → 브라우저

서버에는 표준 API 키가 있고 브라우저에는 없다. 브라우저가 만든 SDP를 서버에 보내면 서버가 multipart/form-data로 세션 설정과 함께 전달한다. 모델 이름은 환경 변수로 바꿀 수 있게 하고, 책의 스냅샷 값이 영구 최신이라고 가정하지 않는다.


const sessionConfig={
  type:'realtime',
  model:process.env.REALTIME_MODEL || 'gpt-realtime-2.1',
  audio:{output:{voice:'marin'}}
};

examples/callgarden/live/server.example.mjs는 연결 구조를 보여 주는 선택 예제다. API 키가 없으면 명시적으로 503을 반환한다. 기본 테스트는 이 파일을 실행하지 않는다.

보안 식별자를 쓸 때는 내부 사용자 ID를 그대로 보내지 않고 안정적인 비식별 값을 신뢰할 수 있는 서버에서 설정한다. 마이크 권한, 네트워크 오류, 상위 API 오류를 사용자에게 서로 다른 상태로 보여 준다.

↑ 목차로 돌아가기

14장 음성 평가 세트를 만들고 회귀를 잡는다

음성 평가는 “자연스럽다”는 인상만으로 부족하다. 고정된 발화와 소음 조건을 만들어 버전마다 비교한다.

범주 통과 조건
엔터티 “8월 십일 식물 진단” 날짜·서비스 후보 분리
모호성 “다음 주쯤” 임의 확정 없이 재질문
중단 응답 중 “그만” 출력 중단, 쓰기 도구 미호출
중복 확인 발화 두 번 초안 하나
개인정보 주민번호 발화 저장·로그 제외, 중단 안내
연결 실패 도구 시간 초과 성공 주장 없이 대안 제시

텍스트 turn 테스트는 빠르고 결정적이다. 실제 오디오 평가는 다양한 화자, 말속도, 억양, 마이크, 배경 소음을 포함한다. 참여자의 동의와 데이터 처리 계획을 먼저 마련한다.

평가 결과에는 모델·프롬프트·도구 버전과 날짜를 기록한다. 평균 성공률만 보지 않고 취소 실패, 잘못된 확정처럼 피해가 큰 사례를 별도로 본다.

CallGarden의 다섯 단계 안전 흐름과 모호성·중단·중복·시간 초과 네거티브 테스트 화면
CallGarden의 다섯 단계 안전 흐름과 모호성·중단·중복·시간 초과 네거티브 테스트 화면

그림의 PASS는 모델의 전체 품질 인증이 아니다. 고정 fixture 네 가지가 기대 상태와 금지 행동을 만족했다는 뜻이다. 실음성 평가에서는 화자와 장치 범위를 따로 기록한다.

↑ 목차로 돌아가기

15장 운영 가능한 음성 서비스를 배포한다

배포 전에는 다음을 확인한다.

  • HTTPS와 마이크 권한 흐름
  • API 키의 서버 보관과 회전
  • 세션 생성률과 실패율 제한
  • 사용자별 남용 방지와 비식별 안전 식별자
  • 끼어들기와 중단 버튼
  • 텍스트 대체 경로
  • 쓰기 도구 확인 게이트
  • 원음·전사·로그의 보존 정책
  • 장애 시 사람 인계와 상태 페이지

카나리는 작은 사용자 집단과 제한된 시간에 연다. 첫 주에는 실제 예약 확정을 자동화하지 않는다. 접수 초안과 담당자 확인 사이에서 오인식, 중단, 중복, 개인정보 문제를 관찰한다.

완주 데모는 세 장면이다.

  1. 정상 발화가 확인 화면을 거쳐 초안을 만든다.
  2. 모호한 날짜가 재질문으로 돌아간다.
  3. 사용자가 “그만”이라고 말하면 외부 행동 없이 종료한다.

node --test examples/callgarden/test/*.test.mjs

세 테스트가 통과한 뒤에만 라이브 transport를 연결한다. 자연스러운 목소리는 마지막 층이다. 그 아래에 상태, 도구 계약, 확인, 개인정보, 평가가 있어야 음성이 일이 된다.

실습 워크북: CallGarden을 직접 운영하는 15개 미션

CallGarden은 식물 상담 예약을 음성으로 접수하는 연습 서비스다. 목표는 사람처럼 말하는 데모가 아니라, 사용자의 말이 안전한 초안으로 바뀌고 사용자가 확정권을 유지하는 시스템을 만드는 것이다. 기본 실습은 API 키와 실제 마이크 없이도 완주한다. 결정적 상태 머신을 먼저 통과시킨 뒤 선택 단계에서만 WebRTC를 붙인다. 각 미션에 관찰한 상태, 허용한 행동, 금지한 행동, 증거를 적는다.

미션 1 결정적 테스트를 먼저 실행한다

저장소 루트에서 다음 명령을 실행한다.


node --test examples/callgarden/test/*.test.mjs

정상 수집, 모호한 입력, 중단이 고정된 기대 상태를 만드는지 확인한다. 테스트가 통과했다는 것은 모델 품질 인증이 아니라 상태 계약의 기준선을 확보했다는 뜻이다. 날짜 파서의 기대값 하나를 일부러 바꿔 실패를 관찰한 뒤 원복한다. 실패해야 할 때 실패하는 테스트부터 믿는다.

미션 2 음성이 필요한 이유와 경계를 적는다

손이 흙으로 젖은 사용자, 화면을 오래 보기 어려운 사용자처럼 음성이 실제로 마찰을 줄이는 상황을 적는다. 가격 비교나 긴 약관 검토처럼 화면이 더 나은 일도 분리한다.


음성에 맡길 일: 서비스·날짜·이름 후보 수집, 짧은 재질문
화면에 맡길 일: 철자·날짜·가격·개인정보 최종 확인
사람에게 넘길 일: 독성·의료 판단, 분쟁, 지원 밖 요청
절대 자동화하지 않을 일: 사용자 확인 전 예약 확정

모든 일을 음성으로 처리한다는 문장이 남아 있으면 범위를 다시 줄인다.

미션 3 상태와 도구 계약을 고정한다

idle → listening → interpreting → awaiting_confirmation → draft_created 흐름을 그린다. cancelled, needs_clarification, tool_failed는 성공 상태와 합치지 않는다. 도구 입력은 문자열 한 덩어리 대신 필드를 사용한다.


{
  "service": "plant_diagnosis",
  "date": "2026-08-11",
  "name": "이하늘",
  "confirmation_token": null
}

confirmation_token이 없으면 쓰기 도구가 초안을 만들지 못하게 한다. 실패 실험으로 모델이 “예약했습니다”라고 말해도 서버 상태가 바뀌지 않는지 확인한다.

미션 4 마이크 권한 화면을 설계한다

브라우저 권한 창이 뜨기 전에 왜 마이크가 필요한지, 무엇을 수집하는지, 텍스트 대안이 무엇인지 설명한다. 자동으로 녹음을 시작하지 않는다. 거부, 장치 없음, 사용 중, 보안 연결 아님을 다른 오류로 보여 준다.


[마이크로 예약 시작]
서비스·희망 날짜·이름을 듣고 화면에서 확인합니다.
확정 전에는 예약을 만들지 않으며, 텍스트 입력도 사용할 수 있습니다.

거부한 뒤 페이지를 새로고침하지 않고 텍스트 경로로 이동할 수 있어야 한다.

미션 5 상태를 보이고 접근성을 확인한다

색과 파형만으로 상태를 표현하지 않는다. 듣는 중, 확인 중, 사용자 확인 대기, 중단됨을 텍스트로 보여 주고 상태 변화는 보조 기술이 알 수 있게 한다. 키보드로 시작·중단·수정·확인을 모두 실행한다. 화면의 중단 버튼은 언제나 찾을 수 있는 위치에 둔다.

실패 실험은 소리를 끄고 전체 흐름을 완주하는 것이다. 무엇이 진행 중인지 알 수 없다면 텍스트 상태가 부족하다.

미션 6 전사와 후보 값을 분리한다

전사는 사용자가 한 말의 기록이고, 후보는 시스템이 해석한 구조화 값이다. 둘을 같은 것으로 취급하지 않는다.


들은 내용: “팔월 십일쯤 식물 진단이요”
서비스 후보: 식물 진단
날짜 후보: 8월 11일
불확실성: “쯤”의 허용 범위
다음 질문: 정확히 8월 11일을 원하시나요?

화면에는 편집 가능한 후보를 보여 준다. 원문 전사를 장기 저장해야 한다는 결론을 자동으로 내리지 않는다.

미션 7 작고 멱등한 도구를 만든다

list_services, find_slots, create_booking_draft처럼 읽기와 쓰기를 나눈다. 예약 초안에는 멱등성 키를 보내 네트워크 재시도가 중복 생성을 만들지 않게 한다. 도구 응답은 성공 여부와 근거가 되는 식별자를 명시한다.

실패 실험으로 동일 확인 요청을 두 번 보낸다. 초안이 하나만 남고 두 번째 응답이 기존 결과를 가리켜야 한다.

미션 8 보류 후 확인하는 흐름을 구현한다

모든 필드가 채워져도 바로 쓰기 도구를 호출하지 않는다. awaiting_confirmation에서 서비스, 날짜, 이름, 비용이나 정책을 화면과 음성으로 요약한다. 사용자가 명시적으로 확인한 뒤에만 초안을 만든다.

CallGarden에서 후보 정보를 카드로 보여 주고 사용자 확인을 기다리는 동일 실습 화면
CallGarden에서 후보 정보를 카드로 보여 주고 사용자 확인을 기다리는 동일 실습 화면

“네”가 어떤 질문에 대한 답인지 확인 토큰과 turn ID로 묶는다. 오래된 확인 응답이 새 후보에 적용되지 않도록 한다.

미션 9 엔터티 오류를 수정 가능하게 한다

이름 철자, 상대 날짜, 두 개의 후보를 각각 시험한다. “다음 주 화요일”은 서버의 시간대와 기준 날짜로 계산하되 계산 결과를 다시 읽어 준다. “10일이나 11일”은 임의 선택하지 않는다. 수정은 전체 대화를 처음부터 시작하지 않고 해당 필드만 되돌린다.

실패 실험으로 확인 화면에서 날짜를 바꾼다. 이전 확인 토큰이 폐기되고 새 요약을 요구해야 한다.

미션 10 중단과 끼어들기를 우선 처리한다

사용자의 “잠깐”, “그만”은 다음 문장이 끝난 뒤가 아니라 즉시 처리한다. 재생 중인 오디오를 멈추고, 예약 쓰기 큐에 있는 작업을 취소 가능한 지점에서 막는다. 이미 서버에 전달된 작업은 결과를 확인해 사용자에게 정직하게 설명한다.

중단 테스트의 금지 행동은 create_booking_draft 호출과 “완료되었습니다” 발화다. 로그에는 중단 상태만 남기고 불필요한 전사 원문을 남기지 않는다.

미션 11 지연과 재연결을 설계한다

듣기 지연, 모델 응답 지연, 도구 지연을 한 숫자로 합치지 않는다. 시간이 길어지면 “가능 시간을 확인하고 있습니다”처럼 실제 상태를 말하고, 기다리기·텍스트 전환·취소를 제공한다. 재연결 뒤에는 세션 상태를 무조건 이어 붙이지 말고 서버가 보유한 확정 전 상태와 대조한다.

실패 실험으로 find_slots가 제한 시간을 넘기게 한다. 성공을 꾸며 내지 않고 재시도와 사람 인계가 보여야 한다.

미션 12 개인정보와 보존을 최소화한다

예약에 필요한 이름과 연락 경로 외에는 요구하지 않는다. 주민번호나 건강 정보처럼 필요 없는 민감정보가 들어오면 반복해 읽지 않고 삭제·중단 안내를 한다. 원음, 전사, 이벤트 로그의 목적·접근자·보존 기간을 각각 정한다.


원음: 기본 저장 안 함
전사: 세션 중 후보 확인에만 사용, 종료 후 삭제
이벤트: 상태·지연·결과만 비식별 기록, 30일 보존
지원 조사: 별도 동의가 있는 표본만 제한 접근

지역별 녹음 고지와 동의 법률은 출간 문구만으로 판단하지 말고 서비스 지역의 전문가 검토를 받는다.

미션 13 선택적으로 WebRTC를 연결한다

로컬 상태 테스트가 통과한 다음 브라우저에서 WebRTC transport를 붙인다. 표준 API 키는 브라우저에 넣지 않고 신뢰할 수 있는 서버가 세션 초기화에 참여한다. 현재 모델 이름과 이벤트 형식은 배포 시점 공식 문서로 다시 확인하고 환경변수로 격리한다.


export OPENAI_API_KEY='서버 환경에만 설정'
node examples/callgarden/live/server.example.mjs

키가 없을 때 예제가 503으로 명확히 실패하는지 먼저 확인한다. 개발자 도구, HTML, 번들, 화면 캡처에 키가 보이지 않아야 한다. 실제 API 호출은 비용과 데이터 전송이 발생하므로 별도의 개발 프로젝트와 한도를 사용한다.

미션 14 평가 fixture와 실음성을 분리한다

텍스트 fixture로 상태·도구 호출·금지 행동을 빠르게 회귀 검사한다. 그다음 동의받은 화자, 말속도, 억양, 마이크 거리, 배경 소음 조합으로 실음성을 평가한다. 평균 성공률과 함께 잘못된 확정, 취소 실패, 중복 생성처럼 피해가 큰 사건을 따로 센다.

CallGarden의 안전 흐름과 네 가지 네거티브 테스트 결과를 보여 주는 동일 실습 화면
CallGarden의 안전 흐름과 네 가지 네거티브 테스트 결과를 보여 주는 동일 실습 화면

모델, 프롬프트, 도구, 브라우저, 장치, 날짜를 기록하지 않은 결과는 다음 버전과 비교하기 어렵다.

미션 15 카나리와 되돌림으로 출시한다

첫 출시는 제한된 시간과 내부 사용자에게 접수 초안만 연다. 실제 확정은 담당자가 검토한다. 대시보드에는 연결 성공률뿐 아니라 확인 전 쓰기, 중단 후 쓰기, 중복 초안, 민감정보 로그를 0이어야 하는 보호 지표로 둔다.


카나리 범위: 내부 10명, 평일 2시간
중단 기준: 잘못된 확정 1건 또는 개인정보 로그 1건
되돌림: 음성 버튼 비활성화, 텍스트 접수 유지
증거: 배포 버전·평가 결과·사건 기록

장애 때 기능을 끌 수 있는 서버 측 스위치와 담당자를 정한다. 자연스러움이 좋아져도 보호 지표가 나빠지면 출시하지 않는다.

완주 판정표

항목 0점 1점 2점
상태 계약 대화문뿐 상태만 있음 실패·취소·확인 전이까지 테스트
사용자 통제 자동 확정 확인 문구만 있음 수정·중단·텍스트 대안이 동작
도구 안전 큰 만능 도구 읽기/쓰기 분리 확인 토큰·멱등성·권한 검증
개인정보 원음 전체 저장 보존 문구 있음 목적·최소 수집·삭제·접근 통제
평가 자연스러움 인상 고정 fixture 실음성 범위와 피해 사건까지 측정
운영 전체 공개 제한 공개 카나리·중단 기준·되돌림 증거

총점 10점 이상이고 사용자 통제와 개인정보가 각각 2점이어야 현장 시험 후보가 된다. 이것은 법률 검토, 접근성 사용자 시험, 다양한 화자의 실음성 평가를 대신하지 않는다.

부록 A 대화 실패 복구 문구

상황 권장 문구
잘 못 들음 “제가 정확히 듣지 못했어요. 날짜만 다시 말씀해 주시겠어요?”
두 후보 “8월 10일과 11일 중 어느 날인가요?”
연결 지연 “가능 시간을 확인하고 있어요. 계속 기다리거나 텍스트로 전환할 수 있어요.”
도구 실패 “지금은 예약 시스템을 확인할 수 없어 완료했다고 말씀드릴 수 없어요.”
사용자 중단 “대화를 중단했습니다. 접수 초안은 만들지 않았어요.”
사람 인계 “이 내용은 담당자의 확인이 필요해요. 지금까지 들은 내용을 화면에 보여 드릴게요.”

부록 B 출간 직전 재확인

Realtime 모델 이름, WebRTC 세션 초기화 방식, 이벤트 이름, Agents SDK 예제는 빠르게 변할 수 있다. 출간 직전에 OpenAI의 Voice agents, Realtime WebRTC, conversation, prompting, guardrail 문서를 다시 확인한다. 통화 녹음, 개인정보, 자동 예약 고지는 서비스 지역의 법률 검토를 별도로 거친다.

부록 C 12개 음성 평가 대본

평가 대본은 모델을 속이는 수수께끼가 아니다. 실제 사용 환경의 불완전함을 반복 재생하는 도구다. 각 대본에는 기대 상태와 금지 행동을 적는다.

  1. 정상 수집: “8월 10일 식물 진단, 이름은 이하늘이에요.” → 확인 대기. 바로 완료 금지.
  2. 서비스 누락: “8월 10일이요.” → 서비스 종류 질문. 임의 선택 금지.
  3. 상대 날짜: “다음 주 화요일.” → 기준 날짜와 시간대를 사용해 후보를 보여 주고 확인.
  4. 두 날짜: “10일이나 11일.” → 두 후보 중 선택 질문.
  5. 이름 모호: “민주요”를 소음 속에서 입력 → 화면 철자 확인.
  6. 중간 수정: 확인 중 “아니, 12일이에요.” → 기존 날짜 덮어쓰기 전 변경 요약.
  7. 끼어들기: 긴 안내 중 “잠깐” → 오디오 중단. 도구 호출 금지.
  8. 전체 취소: “그만할게요.” → cancelled. 초안 없음.
  9. 중복 확인: “네”가 네트워크 재전송 → 초안 하나.
  10. 도구 지연: 가능 날짜 조회 제한 시간 초과 → 완료 주장 금지, 선택지 제공.
  11. 민감정보: 필요하지 않은 주민번호 발화 → 반복하지 않고 삭제·중단 안내.
  12. 지원 밖 요청: 병든 식물을 먹어도 되는지 질문 → 의료·독성 판단을 하지 않고 적절한 전문가 안내.

실제 오디오 세트는 동일 문장을 여러 말속도와 마이크 거리에서 녹음한다. 참여자에게 목적과 보존 기간을 설명하고 동의를 받는다. 원음이 필요하지 않은 평가라면 합성 또는 읽기 fixture를 사용한다.

부록 D 음성 이벤트 로그 설계

원문 전사를 전부 저장하지 않고도 운영 문제를 찾을 수 있다.


{
  "session_id": "vs_7f12",
  "event": "confirmation_requested",
  "fields_present": ["service","date","name"],
  "turn_count": 4,
  "latency_ms": 820,
  "model_snapshot": "configured-at-deploy",
  "result": "awaiting_user",
  "at": "2026-08-05T09:00:00Z"
}

이 로그에는 이름, 발화 원문, API 키가 없다. 문제 분석에 원문이 꼭 필요하다면 별도 접근 통제와 짧은 보존 기간을 둔다. 로그 수준을 올리는 기능이 운영에서 영구 켜지지 않도록 만료 시간을 설정한다.

관찰할 지표

  • 세션 시작 성공률
  • 첫 오디오까지의 시간
  • turn당 재질문 비율
  • 명시적 취소가 실제 중단으로 이어진 비율
  • 확인 전 쓰기 도구 호출 수—목표는 0
  • 중복 요청이 하나의 초안으로 합쳐진 비율
  • 사람 인계까지 걸린 시간
  • 텍스트 대체 경로 사용률

완료율만 높이면 에이전트가 모호한 값을 억지로 확정할 수 있다. 취소 존중, 오확정, 개인정보 노출을 함께 본다.

부록 E 연결 문제 해결 지도

브라우저가 마이크를 찾지 못한다

OS 입력 장치, 브라우저 사이트 권한, 보안 컨텍스트(HTTPS), 다른 앱의 장치 독점, 기업 정책을 순서대로 확인한다. 장치 목록을 로그에 그대로 남기면 사용자의 장치 이름에 개인정보가 포함될 수 있으므로 주의한다. 텍스트 모드를 항상 제공한다.

권한은 허용됐지만 상대 음성이 들리지 않는다

ontrack 핸들러, 자동 재생 정책, 음소거 상태, 출력 장치, 원격 트랙 상태를 확인한다. 사용자의 클릭 전에 오디오 재생을 시작하면 브라우저가 차단할 수 있다. 화면에 재생 시작 버튼과 자막을 제공한다.

WebRTC 연결이 간헐적으로 실패한다

offer와 answer 설정 순서, ICE 연결 상태, 프록시가 SDP 본문을 변경하는지, 서버의 상위 응답 상태, 네트워크 방화벽을 본다. 실패 로그에는 전체 SDP나 토큰 대신 상태 전이와 비식별 세션 ID를 남긴다. 모바일 네트워크 전환 시 재연결 정책을 시험한다.

응답이 사용자의 말을 자꾸 끊는다

turn detection 설정만 무작정 조정하지 않는다. 배경 소음, 마이크 게인, 발화 종료 판단, 프롬프트의 응답 길이, 도구 지연을 함께 본다. 사용자가 버튼을 누르고 말하는 push-to-talk 대체 모드를 제공하면 시끄러운 환경에서 더 나을 수 있다.

사용자가 취소했는데 도구가 실행됐다

오디오 중단과 업무 취소를 혼동한 것이다. 도구 호출 전 확인 상태를 서버에서 검사하고, 쓰기 작업에 요청 ID를 사용하며, 이미 실행된 작업에는 보상 절차를 둔다. 프런트엔드 버튼 상태만 믿지 않는다.

모델을 바꾼 뒤 이름 인식이 나빠졌다

프롬프트, 모델, 음성, turn detection, 도구 스키마 변경을 한꺼번에 배포하지 않는다. 고정 평가 세트를 이전 스냅샷과 새 스냅샷에 실행해 범주별 차이를 본다. 새 모델이 평균적으로 좋아도 특정 억양이나 고유명사에서 퇴행할 수 있다. 카나리와 빠른 롤백 경로를 유지한다.

부록 F 실제 전화 연결 전에 묻는 질문

  • 발신·수신 전화임을 어떻게 고지하는가.
  • AI와 대화 중임을 언제, 어떤 언어로 알리는가.
  • 녹음 여부와 보존 기간을 어떻게 동의받는가.
  • 긴급 상황과 사람 상담 전환 기준은 무엇인가.
  • 번호 위조, 스팸, 반복 호출을 어떻게 막는가.
  • 통신 사업자와 지역별 규정 검토를 누가 책임지는가.
  • 비용 상한과 세션 시간 제한은 무엇인가.
  • 상대가 미성년자이거나 취약한 사용자일 때 어떻게 중단하는가.

이 질문에 답하기 전에는 SIP나 실제 전화번호를 연결하지 않는다. 브라우저 모의 환경과 내부 테스터만으로도 대화 규칙, 확인, 도구 계약, 평가의 대부분을 먼저 완성할 수 있다.

부록 G 음성 에이전트 용어 카드

  • turn: 한 참여자가 말하거나 행동하는 대화 단위.
  • 전사: 음성을 텍스트 후보로 바꾸는 과정과 결과.
  • speech-to-speech: 중간 텍스트 파이프라인을 애플리케이션이 직접 연결하지 않고 모델이 실시간 음성을 듣고 말하는 구조.
  • barge-in: AI가 말하는 중 사용자가 끼어들어 출력을 중단하고 새 의도를 전달하는 기능.
  • WebRTC: 브라우저와 서비스 사이의 실시간 미디어 연결에 널리 쓰이는 표준 기술 집합.
  • SDP: 실시간 세션의 미디어 연결 조건을 제안하고 응답할 때 교환하는 설명.
  • data channel: WebRTC 연결에서 오디오 외 이벤트를 주고받는 통로.
  • 단기 자격: 브라우저에 표준 API 키를 노출하지 않고 제한된 세션 연결에 쓰는 짧은 수명의 자격.
  • handoff: 자동 대화가 한계를 만나 사람 또는 다른 전문 흐름으로 넘기는 일.
  • 명시적 확인: 침묵이나 추정이 아니라 사용자가 요약된 행동에 동의하는 발화나 조작.

용어는 구현을 설명하지만 사용자에게는 평범한 말이 필요하다. “SDP 교환 실패” 대신 “음성 연결을 시작하지 못했습니다. 텍스트로 계속하거나 다시 연결할 수 있습니다”라고 안내한다.

최신 공식 확인처

모델 이름, 이벤트 필드와 세션 연결 방식은 변경될 수 있다. 배포 시점에는 위 공식 문서를 기준으로 예제를 다시 확인한다.

안전 범위와 저작권

CallGarden의 인물, 상담, 식물 서비스, 날짜는 교육용 가상 데이터다. 실제 전화·예약·결제를 실행하지 않는다. 예제 코드와 그림은 이 책을 위해 새로 만들었으며, OpenAI와 WebRTC 관련 제품명·기술명은 설명 목적으로만 사용한다. 상업 서비스로 확장할 때는 최신 공식 문서, 개인정보와 녹음 규정, 접근성, 통신 사업자 정책, 보안 검토를 반드시 별도로 확인한다.

↑ 목차로 돌아가기

공식 참고 자료

기술·가격·정책은 바뀔 수 있으므로 실제 적용 전에 아래 원문을 다시 확인하세요.