FROM THE WORKSHOP / 노하우
전화 AI를 검증하는 법 — 내 컴퓨터의 소리에서 상대의 답변까지
로컬 파형과 일반전화 상대 수신은 다르다. Apple 실시간 말하기로 세제 질문의 응답을 확인한 조건, 공개 구현을 읽는 기준, JEV·Fable의 역할과 수동 재생 지연, 아직 검증하지 못한 모델 대화 왕복을 정리했다.
전화 AI를 검증하는 법 — 내 컴퓨터의 소리에서 상대의 답변까지
2026-10-11 · 음성 AI/자동화 · T-261001-003
전화 AI에서 가장 오래 막힌 곳은 답변을 만드는 모델이 아니었다. 내 컴퓨터에서 만든 음성을 일반전화 상대에게 보내는 경로였다. 파형도 생기고 음성 인식도 됐지만, 상대는 아무 말도 듣지 못했다.
Apple 실시간 말하기로 보낸 세제 질문에 세탁소 시험 봇이 구체적으로 답하면서 한 경계를 넘었다. 이후 Fable이 만든 문장을 재생하는 데까지 진행했다. 모델 발화 뒤 상대의 답변은 아직 받지 못했다. 이 기록은 완성된 전화 봇의 설치 설명서가 아니라, 실패를 구분하고 검증 순서를 정리한 현장 기록이다.
1. 성공을 다섯 단계로 나눈다
| 단계 | 확인할 것 | 이것만으로는 부족한 근거 |
|---|---|---|
| 음성 생성 | 문장과 PCM·음성 파일의 내용 | 파일 생성 성공 |
| 통화 경로 | 실제 통화가 쓰는 입력·출력과 재생 시점 | 시스템 기본 마이크 이름 |
| 상대 수신 | 보낸 질문에 맞는 상대의 구체적인 반응 | 내 스피커에서 들리는 소리 |
| 모델 연결 | 실제 상대 발화로 승인한 모델이 만든 다음 문장 | 합성 예제로 만든 답변 |
| 모델 대화 왕복 | 그 모델 문장을 보낸 뒤 상대가 이어서 답함 | 답변 JSON이나 재생 버튼의 상태 |
서로 다른 통화에서 성공한 조각을 이어 붙여 한 번의 모델 대화가 성공했다고 보고하지 않는다. 통화 식별자와 발화 순서를 함께 남겨야 한다.
2. 오디오의 원리는 입력 경로에 있다
문장을 음성으로 합성하면 오디오 샘플이 생긴다. 로컬 스피커로 보내면 내 컴퓨터에서 소리가 난다. 전화 상대가 들으려면 그 샘플이 통화의 송신 경로에 들어가야 한다.
가상 오디오 장치는 출력을 다른 입력으로 전달한다. 통화 앱은 입력을 선택하고 음성 처리를 적용할 수 있다. 장치에 신호가 있어도 앱이 그 장치를 사용하지 않거나 처리 중 신호가 줄면 상대가 듣는 결과는 달라진다.
실제로 별도 로컬 VoiceProcessingIO 시험에서는 음성이 크게 줄었고, 처리를 우회하자 회복됐다. BlackHole 개발자의 브라우저 사례에도 에코 제거로 신호가 상쇄되는 설명이 있다. 로컬 재현과 다른 앱의 사례는 진단 단서다. 실제 전화 앱의 원인 판정은 별도로 해야 한다. 개발자 설명
3. 공개 구현은 통화 종류와 증거부터 읽는다
“전화 봇이 된다”는 설명만으로 코드를 가져오지 않는다. 먼저 아래 네 가지를 찾는다.
- 통화 종류: 일반 이동통신 회선, FaceTime, SIP 중 무엇인가.
- 방향: 상대 음성을 받는가, 우리 음성을 보내는가, 둘 다 하는가.
- 증거: 로컬 파형인가, 원격 녹음인가, 상대가 답한 실제 통화인가.
- 도입 조건: 기존 환경에 맞는가, 권한·설치·운영·라이선스 검토가 필요한가.
예를 들어 facetime-bridge의 통합 문서는 FaceTime Audio의 양방향 통화를 설명한다. 그 작성자가 보고한 성공은 참고할 만하다. 이를 iPhone 회선 중계의 검증 결과로 사용할 수는 없다. 이번 작업에는 해당 코드를 도입하지 않았다. 통합 문서
독자 드라이버 작성도 필수는 아니다. Apple 실시간 말하기처럼 통화 참가자에게 음성을 전달하도록 제공된 기능이 있는지 먼저 살핀다. 지원되는 기능이라는 사실과 내 환경에서의 성공은 다시 구분한다. Apple 실시간 말하기 안내
4. 짧고 구체적인 질문으로 상대 수신을 확인한다
이번에는 AI 비서 소개와 시험 녹음 안내 뒤에 “세탁기에 세제가 자동으로 들어가나요?”라고 물었다. 상대는 세제가 자동으로 투입된다고 답했다. 단순한 “네”나 안내 방송보다 보낸 내용과의 연결이 분명했다.
성공 당시에는 통화 전에 한국어 음성을 한 번 재생했다. 통화 중 Apple의 음성 입력·출력 경로도 확인했다. 이것은 재현할 조건으로 남긴다. 예열 여부만 바꾼 대조 실험을 하지 않았으므로 예열 하나가 해결 원인이라고 단정하지 않는다.
5. JEV는 이미 있다. 다음 병목은 재생 연결이다
현재 베리의 JEV 경로는 최신 상대 발화를 분류한다. 인사·자기소개처럼 준비된 답으로 충분하면 그 답을 고른다. 문맥이나 새 답변이 필요하면 Fable로 넘긴다. 이전 로컬 시험에서 준비 답변은 약 0.21초였다. 이 수치는 전화 상대가 듣기까지의 시간이 아니다.
마지막 실통화의 음성 인식부터 Fable 질문 생성 완료까지는 5.507초였다. 하지만 질문 재생은 발신 후 약 40초에 시작했다. 두 측정의 시작점은 다르다. 발신 이후에는 연결과 상대 인사를 기다리는 시간도 들어 있다. 생성 후 사람이 도구 결과를 읽고 화면에 문장을 옮겨 재생하는 절차도 지연을 만들었다.
따라서 JEV를 더 호출하는 것만으로 이 병목을 없앨 수 없다. JEV가 고른 답도 같은 수동 절차를 거치면 늦게 재생된다. 모든 입력에 분류 호출을 하나 더 붙이면 처리 시간이 늘 수도 있다.
개선 방향은 다음과 같다. 아래 연결은 아직 구현·실통화 검증을 마치지 않았다.
상대 발화 → 음성 인식 → JEV의 준비 답 선택 또는 Fable의 문장 생성
↓
준비된 문장을 송출 경로에 전달
↓
상대의 후속 답변 확인
JEV는 제한된 준비 답을 빠르게 고르는 역할을 유지한다. Fable은 문맥이 필요한 질문을 만든다. 지연 개선의 우선 대상은 생성 결과를 재생으로 넘기는 연결이다. 상대가 말하는 동안의 재생, 중복 재생, 끝난 통화에 남은 문장을 보내는 문제도 함께 검증해야 한다.
6. 다른 전화 봇은 어떻게 연결하는가
공개 구현 세 종류의 문서와 코드를 확인했다. 공통점은 모델 출력에서 통화 오디오까지 프로그램이 이어서 처리한다는 것이다. 발화마다 사람이 결과를 읽고 재생 버튼을 누르지 않는다. 아래는 소스 조사 결과이며, 이 환경에서 세 구현을 실행해 성능을 측정한 결과는 아니다.
| 구현 | 병목을 줄이는 방식 | 베리에 적용할 때의 경계 |
|---|---|---|
| Pipecat의 일반전화 예제 | STT → LLM → TTS → 통화 출력이 한 파이프라인이다. 오디오 프레임을 전화 사업자의 스트림으로 보낸다 | Twilio Media Streams를 쓰는 예제다. 현재 iPhone 회선 중계에 그대로 연결되는 코드는 아니다 |
| LiveKit Agents | 음성 프레임을 출력으로 계속 전달한다. 상대의 발화 종료가 확정되기 전에 생성을 시작하는 선택지도 둔다 | 일찍 만든 답은 입력이 달라지면 취소해야 한다. 현재 Mac 송출 경로의 자동 연결을 대신하지 않는다 |
| facetime-bridge | PLAYBACK 패킷을 받으면 네이티브 재생 큐에 PCM을 넣는다. CLEAR로 대기 음성을 버린다 | 문서의 성공 범위는 FaceTime Audio다. 우리 일반전화에서의 성공 근거는 아니다 |
Pipecat 예제의 Pipeline에는 llm, tts, transport.output()이 연속으로 들어 있다. 통화 연결 이벤트가 첫 대화를 시작한다. Twilio 직렬화 코드는 오디오를 media 메시지로 바꾸고, 끼어들기 이벤트를 clear로 바꾼다. 종료 이벤트에는 실제 통화 종료 요청도 연결한다. 전화 예제 코드 · 오디오·중단·종료 코드
전체 답변이 끝날 때까지 기다릴 필요도 줄인다. Pipecat은 기본적으로 스트리밍 텍스트를 문장 단위로 모아 합성한다. 지원 서비스에서는 토큰 단위 전달도 선택할 수 있다. 준비된 인사는 LLM을 거치지 않고 TTS에 바로 보낼 수 있다. 베리의 짧은 한 문장 질문에서는 먼저 수동 전달을 없애는 편이 우선이다. 문장 분할의 추가 이득은 그다음 측정할 항목이다. Pipecat TTS 문서
LiveKit은 발화 종료 판정 전의 생성과 선택적인 TTS 선행 처리를 제공한다. 그만큼 취소된 계산이 생길 수 있다. 빨리 말하려고 침묵 대기만 줄이면 상대가 아직 생각 중인데 끼어들 수 있다. 이 기능은 출력 연결과 취소가 작동한 뒤 평가할 최적화다. 발화 전환 조정 · 음성 프레임 전달 코드
Mac에 가까운 facetime-bridge도 오디오를 재생할 때마다 화면을 조작하지 않는다. 스트림 수신기가 enqueuePlayback을 호출하고, 네이티브 엔진은 scheduleBuffer로 이어서 재생한다. 패킷 수신 코드 · 재생 큐 코드
이 조사에서 얻은 적용 순서는 현재 성공한 송출 경로의 자동 전달 → 끼어들기·통화 종료 때 재생 취소 → 문장 단위 스트리밍 → 필요할 때 선행 생성이다. JEV는 준비된 답을 고르는 역할로 유지할 수 있다. 조사한 코드의 채택이나 유료 전화 서비스 전환은 실행하지 않았다. 기존 실시간 말하기를 자동으로 제어할 수 있는지와 일반전화의 원격 수신은 별도 검증 과제다.
7. 한 개의 초시계로 모든 성능을 말하지 않는다
| 시점 | 의미 |
|---|---|
| 발신 요청 | 전체 통화 예산의 시작 |
| 상대 발화 끝 | 응답 지연을 재기 위한 기준 |
| 음성 인식 완료 | 받아쓰기 비용 |
| 첫 토큰·문장 완료 | 모델 응답 준비 비용 |
| 실제 재생 시작 | 화면 조작·대기·송출 연결 비용 |
| 상대의 다음 발화 | 질문 수신과 대화 지속의 근거 |
| 종료 요청·종료 확인 | 제한 시간 준수 여부 |
모델 3.4초, 5.5초라는 기록은 로컬 처리 구간이다. 상대 귀까지의 지연으로 보고하지 않는다. 캡처 제한도 통화 제한을 대신하지 않는다. 마지막 시험은 종료 요청이 발신 후 64.5초에 이뤄져 60초 약속을 넘겼다. 다음 검증 전에 실제 종료 동작과 확인 경로를 준비해야 한다.
8. 결과와 미완료를 함께 남긴다
현재 확보한 결과는 일반전화에서 준비한 질문의 전달과 상대의 구체적인 답변이다. Fable이 실제 상대 인사로 질문을 만들고 그 문장을 재생한 것도 확인했다. 그러나 그 모델 발화에 대한 상대의 후속 응답은 미확인이다.
기록에는 통화별 질문·상대 답변·실제 모델·측정 구간·종료 결과를 남긴다. 공개 글에는 기술적 판단에 필요한 내용만 적는다. 전화번호와 원본 녹음은 공개하지 않는다.
로컬에서 된 단계는 그 단계의 성공으로 인정한다. 상대의 답변이 필요한 단계는 실제 답변으로 확인한다.