본문으로 건너뛰기Hwanseok Sim

작성일

Gem3Park 개발기 2 — 상담원 인계 안전장치와 1,400줄 클로저 해체

동·호수를 세 번 못 알아들으면 상담원에게 넘기는 규칙을 넣으며, 프롬프트에 맡긴 흐름의 한계와 1,400줄짜리 함수를 CallFlowController·SessionState로 쪼갠 과정을 적었다.

Gem3Park 개발기 시리즈 2/8편. 전체 목록은 gem3park 태그에서 볼 수 있다.

인계 규칙을 처음 넣던 날

1편의 릴레이는 전화를 받고 끊을 수는 있었다. 문제는 알아듣지 못했을 때였다. 알아듣지 못한 채로 차단기를 열 수는 없고, 그렇다고 통화를 그냥 끊을 수도 없어서 사람에게 넘기는 길이 필요했다.

2월 21일에 3-strike 규칙을 넣었다. 동·호수가 확인되지 않은 채 세 번 물으면 상담원 전환으로 빠진다. 당시 코드는 이랬다.

src/asterisk/audiosocket-server.ts
buildingUnitAskCount++;
if (buildingUnitAskCount >= 3) {
  forceAgentRedirect = true;
  waitingForBuildingUnit = false;
  pendingCommands = [];
  // 모델에게 상담원 연결 멘트와 전환 명령을 내리라고 시스템 메시지를 보낸다
  bridge.sendText("사용자가 동호수를 3회 이상 잘못 말했습니다. ... 상담원에게 전환명령을 내려주세요.");
}

횟수는 코드가 세지만, 정작 상담원 전환 멘트와 명령은 모델에게 시키고 있다. 이튿날(2월 22일)에는 같은 규칙을 방문 목적에도 넣었다. 동·호수를 확인한 뒤에도 방문 목적이 불분명하면 다시 묻고, 세 번 이상 불분명하면 상담사로 넘긴다. 같은 규칙인데 프롬프트와 런타임 코드를 따로 고쳐야 했다.

모니터링 웹 1차와 종료 로그 정리

같은 날 실시간 모니터링 웹 1차를 올렸다. 릴레이 로그를 파일에 항상 쓰고, Asterisk 로그와 토큰 사용량, 발신 SIP 이벤트를 웹에서 보게 하는 작은 서버였다. 모니터 서버와 화면 파일 6개로 시작했다. 모델에 무엇이 들어갔는지 로그가 자꾸 빠져서 입력 로그 전용 패널도 따로 붙였다. 2월 말에는 시뮬레이터의 입차 기록도 화면에 띄웠다.

통화 종료에도 문제가 있었다. 릴레이가 먼저 HANGUP을 보낸 통화에서 소켓 닫힘이 ECONNRESET로 올라와 오류처럼 기록되고, 정리 로직이 연쇄로 돌았다. 2월 25일에 릴레이가 끊은 세션을 hangupInitiatedSessions에 사유와 함께 적어 두고, 소켓 close나 error가 오면 그 사유를 먼저 보게 했다. 정상 종료를 오류로 세던 로그가 정리되자 통화 요약을 믿고 볼 수 있었다.

1,400줄짜리 클로저

2월 27일에 이 함수를 쪼갰다. 그 시점의 initializeSession()은 1,400줄짜리 클로저였고 안에 지역 변수 20개가 흩어져 있었다. 동호수 대기 플래그, 질문 횟수, 누적 전사, 방문 목적 확인 여부 같은 것들이다. 규칙을 하나 넣을 때마다 이 함수 안에서 플래그가 늘었다.

이전이후
initializeSession() 1,400줄 클로저CallFlowController 클래스
지역 변수 20개SessionState 클래스 프로퍼티
오디오 큐잉·전송AudioPipeline 클래스
텍스트 분석 함수 30여 개text-analysis.ts 모듈
세션별 Map<string, ...> 12개하나의 Map<string, ActiveSession>

상태를 한 구조체로 모은 뒤로는 허용 목록 검사 같은 보강을 SessionState에 필드를 더하고 CallFlowController에서 전이를 막는 식으로 얹었다.

거절했을 때 무엇을 남길 것인가

2월 28일의 수정은 작았다. 최종 확인 단계에서 운전자가 "아니요"라고 답하면 전부 처음부터 다시 묻고 있었다. 그런데 대개 틀린 것은 방문 목적 하나였다. 그래서 거절하면 방문 목적만 초기화하고(resetGuidedVisitPurposeOnly) 동·호수는 유지했다. 방문 목적을 다시 받으면 저장된 동·호수로 출입 명령을 다시 만든다.

같은 커밋에서 응답 속도를 높이려고 프롬프트도 줄였다. 전후 지연은 재지 않았다.

다음 글

Gem3Park 개발기 3 — guided 모드와 방문 목적 판정

이전 글: Gem3Park 개발기 1 — AudioSocket과 Gemini Live로 인터폰 전화 받기