FROM THE WORKSHOP / 노하우
전화 AI 베리, 처음으로 상대와 말을 주고받다 — 이틀의 무음을 하루 만에 뚫은 기록
맥에 붙인 아이폰 회선으로 AI 비서가 전화를 걸어 상대 봇과 질문·답변을 주고받은 첫 통화. 화면 조작 손을 컴퓨터 사용 도구에서 접근성 API 헬퍼로 바꿔 1.2초를 0.1초로, 통화 중 답변 모델을 Haiku로, 단일 턴을 턴 루프로 바꿨다. 알림 센터 통화창·전화 앱·권한 컨텍스트의 함정과 상대 말끝 기준 실측 숫자를 남겼다.
전화 AI 베리, 처음으로 상대와 말을 주고받다 — 이틀의 무음을 하루 만에 뚫은 기록
2026-10-13 · 음성 AI/자동화 · T-261001-003
맥에 붙인 아이폰 회선으로 AI 비서 「베리」가 전화를 걸고, 상대의 말을 듣고, 답하고, 상대가 다시 답하는 왕복이 처음 성공했다. 상대는 국내 공개 AI 데모 번호(세탁소 상담 봇)다. 베리가 세제가 자동으로 들어가는지 묻자 봇이 「네, 세제는 자동으로 투입됩니다」라고 답했고, 베리는 감사 인사를 하고 전달하겠다고 말했다.
이틀 전까지는 같은 구성으로 전화를 걸어도 베리가 한마디도 못 하거나, 한 마디만 하고 침묵했다. 바뀐 것은 모델도 음성도 아니고 손과 귀와 순서였다. 그 차이를 적는다. 검증 기준은 앞 글의 다섯 단계를 그대로 쓴다.
1. 구성은 그대로, 바꾼 것은 세 가지
| 부품 | 전(10/11~12) | 후(10/13) |
|---|---|---|
| 송출 | Apple 실시간 말하기(Live Speech), 지안 프리미엄 음성 | 동일 |
| 수신·받아적기 | 통화 앱 프로세스 탭 → 로컬 Whisper | 동일 (발화 파일을 원자적으로 넘기고 말끝 시각을 같이 적음) |
| 화면 조작 손 | 코덱스 전용 컴퓨터 사용 도구(CUA): 화면을 찍어 읽고 클릭 | 접근성 API 헬퍼: 창의 요소를 직접 읽고 누름 |
| 답변 모델 | Opus 하나 | 첫 인사·요약은 Opus, 통화 중 답변은 Haiku |
| 대화 구조 | 한 번 말하고 45초에 끊는 단일 턴 | 턴 루프: 상대 말마다 답, 끊자는 말에 인사, 상대가 끊으면 감지 |
2. 손을 바꾸면 1.2초가 0.1초가 된다
CUA는 사람처럼 화면을 찍어 읽고 클릭한다. 답 문장이 준비된 뒤 재생 버튼을 누르기까지 1.2초, 재생 상태를 확인하기까지 2.5초가 들었다. 접근성 API로 같은 창의 텍스트 필드와 버튼을 직접 다루면 글 넣기 40ms, 누르기 82ms, 재생 확인 98ms다. 카카오톡 자동 전송에 이미 쓰던 방식이라 새 권한도 아니다.
실시간 말하기 창은 접근성 트리에서 AXTextField(입력), play.fill(말하기), pause.fill(일시 정지), xmark.circle.fill(말하기 취소)로 보인다. 말하는 동안 입력 필드는 비고, 말한 문장이 AXStaticText로 나타난다. 이 모양을 조건으로 「지금 말하는 중인지」를 판단한다.
3. 모델을 바꾸면 4초가 1초가 된다
같은 경로로 재어 보니 첫 글자가 나오기까지 Opus 1.6~7.3초, Haiku 0.7~1.4초였다. 상대 말끝에서 답 소리까지의 예산은 말끝 판정 0.4초 + 받아적기 0.4~0.7초 + 모델 + 누르기 0.2초 + 실시간 말하기 합성 시작 0.9~1.0초다. 모델이 1초를 넘기면 3초 안에 들어갈 수 없다. 통화 중 짧은 답은 Haiku에 맡기고, 첫 인사(미리 만들어 둠)와 통화 뒤 요약은 Opus를 그대로 둔다.
모델 한 프로세스를 통화 전에 한 번 깨워 둔다. 첫 요청은 1.9초, 그 뒤는 0.7~1.0초였다. 첫 문장이 끝나는 순간 바로 재생하고 나머지 문장은 뒤따라 재생한다. JEV 라우팅(0.2초)은 빠른 턴에서 건너뛴다.
4. 순서가 틀리면 아무 말도 안 나간다
첫 성공 통화 직전의 실패는 모두 순서 문제였다.
- 첫 발화 게이트가 「발신 후 25초」 절대 시각이라, 봇의 긴 안내가 끝나기 전에 닫혔다. 상대 말끝 기준 창으로 바꿨다.
- 첫 인사를 준비하는 길과 대화 턴을 돌리는 길이 따로였다. 봇의 초대말(「무엇을 도와드릴까요」)이 대화 턴으로 흘러가 준비된 첫 질문이 끝내 풀리지 않았다. 초대말은 첫 질문을 푸는 신호로, 그 전의 발화는 답하지 않도록 바꿨다.
- 답이 늦을 때 먼저 내보내는 「네, 잠시만요」가 정작 답이 오면 길을 막았다. 답이 오면 호응문을 취소하고 바로 답한다.
- 상대가 먼저 끊으면 90초 마감까지 모르고 있었다. 2초마다 통화창을 확인해 끊김을 감지한다.
5. 통화창은 어디에 뜨는가
맥의 전화 앱에서 걸면 앱이 앞에 있는 동안은 알림 센터 통화창이 뜨지 않는다. 발신 직후 전화 앱을 숨겨야 알림 센터에 통화 배너(AXNotificationCenterBanner, 식별자 FACETIME_NOTIFICATION)가 생기고, 그 안에 상대 번호, 「사용자의 iPhone 사용」, 0:12 같은 타이머, 종료 버튼이 있다. 연결 판정과 끊기는 이 배너로만 한다.
한 가지는 원인을 못 밝혔다. 같은 배너를 오래 살아 있는 헬퍼 프로세스는 15초 내내 못 봤고, 요청마다 새로 띄운 프로세스는 2초 만에 봤다. 세 통화에서 같게 재현됐다. 통화창 읽기만 매번 새 프로세스로 돌리는 것으로 닫았다.
전화 앱도 함정이 있다. 숨겼다가 다시 보이면 내용이 비어 키패드가 안 잡힌다. 발신 전에 앱을 다시 보이게 하고, 키패드 화면이 렌더될 때까지 기다린 뒤 번호를 누르고, 표시된 번호를 읽어 승인된 번호와 같을 때만 통화 버튼을 누른다.
6. 어느 창에서 돌리느냐가 권한을 가른다
- 맥에 SSH로 들어온 셸은 접근성 권한은 있지만 키체인이 잠겨 Claude 로그인이 없다. 모델을 쓰는 관찰자는 화면 로그인 세션의 터미널(tmux 창)에서 띄우고 로그 파일을 읽는다.
- 반대로 실시간 말하기를 켜는 설정 스위치는 SSH에서 띄운 헬퍼로는 2초 만에 켜지는데, tmux 창에서 띄운 헬퍼로는 스위치만 바뀌고 앱이 뜨지 않았다. 켜기는 SSH에서, 통화는 tmux에서 한다.
- 시스템 설정은 처음 켤 때 10초 넘게 걸린다. 스위치를 못 찾으면 창을 다시 열며 30초까지 기다린다.
7. 숫자로 남긴 것
| 구간 | 실측 |
|---|---|
| 발신 → 알림 센터 배너 연결 관측 | 4.3초 |
| 봇 초대말 → 첫 질문 재생 | 19.2초(발신 기준) |
| 상대 말끝 → 답 재생 누름 | 3.08초 |
| 상대 말끝 → 답 소리 시작 | 4.1초 |
| 로컬 리허설(Haiku) 상대 말끝 → 답 소리 | 3.3~3.4초 |
| 실시간 말하기 누름 → 소리 | 0.9~1.0초(음성 교체로 안 줄어듦) |
목표였던 3초는 아직 못 넘었다. 남은 것은 답을 한 문장으로 묶어 다음 답이 밀리지 않게 하기, 질문이 끝나면 베리가 먼저 인사하고 끊기, 말하는 중에 받아적어 말끝 뒤 작업을 줄이기, 녹음 자동 전달의 시계 검사 완화다. 사람 상대 통화는 별도 승인 뒤에 한다.
8. 재현 순서
- 실시간 말하기를 켜고 짧은 문장을 한 번 재생해 음성을 깨운다.
- 관찰자(받아적기·모델)와 2트랙 녹음 헬퍼를 띄우고 준비 영수증을 기다린다.
- 전화 앱 키패드에 번호를 누르고 표시값을 읽어 확인한다.
- 통화 버튼을 한 번 누르고 전화 앱을 숨긴다. 알림 센터 배너의 타이머가 보이면 연결이다.
- 녹음을 시작하고, 봇 초대말에 첫 질문을 재생한다. 이후 상대 말마다 답하고, 끊자는 말이나 마감 6초 전에 인사한다.
- 끝나면 실시간 말하기를 끄고 전화 앱을 닫는다. 녹음 두 트랙과 턴별 시각은 그대로 남긴다.
전화 한 통은 승인 한 번이다. 자동응답 봇을 상대로 할 때만 「될 때까지」 반복했고, 실패한 통화 다섯 건의 로그가 성공의 재료였다.