대화형 항공권 음성 검색 서비스 개발 이야기
IT/예비창업

대화형 항공권 음성 검색 서비스 개발 이야기

반응형

 

시발점

 

2018년 11월 친구와 함께 한 공모전을 나갔다.

SK텔레콤의 AI 음성 스피커인 NUGU를 활용한 아이디어를 제시하거나 개발 프로토타입을 만들어 제출하는 공모전이었다. 처음 이 공고를 페이스북에서 우연히 발견했는데 그 날 친구에게 이 공모전에 대해 설명했고 바로 아이디어 회의에 들어가서 얼마 지나지 않아 친구에게서 한 아이디어가 나왔다. '항공권 음성 검색' 이었다. 나도 이 아이디어가 재미있을 것 같아서 동의했지만 우리는 개발 부문으로 지원할 예정이었기 때문에 먼저 항공권 데이터에 대한 문제를 해결해야만 했다. 그때 가장 먼저 떠오른 건 스카이스캐너 사이트를 파싱하는 것이었다. 스카이스캐너는 국내 및 국제의 여러 항공사와 여행 예약 사이트들의 항공권 가격을 비교하여 제공해주는 플랫폼이다. 우리가 목표로 했던 것은 (나중에 더 세분화되긴 했지만) '최저가 검색' 이었기 때문에 이런 것들이 이미 API로 통합되어 있는 스카이스캐너가 좋은 타깃이 되었다. 스카이스캐너의 항공권 API를 직접 사용 할 수 있다면 최고였겠지만 아쉽게도 개인에게는 제공되지 않았다. 다행히 나는 크롤링과 봇에 관심이 많아서 웹 사이트를 분석해서 카카오톡 봇과 텔레그램 봇을 개발했던 경험이 있었고 이 경험을 바탕으로 스카이스캐너의 사이트를 분석하기 시작했다.

 

 

 

 

스카이스캐너 사이트 분석

 

항공권 데이터는 방대했다. 스카이스캐너에서 개발자 도구를 열어 항공권 검색을 해보면 무수히 많은 요청들이 오고 간 것을 볼 수 있다.

 

[스카이스캐너] 항공권 관련 모든 요청

 

네트워크 탭에서 요청들을 하나하나 클릭해 보면서 항공권과 관련된 데이터가 어느 요청에 담겨 오는지 찾다가 눈에 띄는 한 요청을 발견했다. (Preview 탭을 눌러보면 응답 데이터를 볼 수 있다.) 살펴보니 우리가 찾던 데이터가 맞았다. 친구와 함께 기뻐하며 이제 이것만 분석하면 항공권 정보를 알려줄 수 있겠다는 희망을 얻었다.

 

[스카이스캐너] 항공권 데이터 요청 응답

 

그러나 이 많은 데이터들의 관계를 파악하는 것은 그렇게 쉽지만은 않았다. 마치 꼬리에 꼬리를 물듯이 데이터들의 관계가 얽혀있거나 중간에 어디로 이어졌는지 모르게 분리되어 있는 구간들이 있었다. 그래도 어떻게든 분석을 해내야 했기에 며칠 동안 붙들고 분석을 했었던 기억이 난다.

 

스카이스캐너는 이와 같은 정확한 날짜의 검색 결과 뿐만 아니라 월 to 월의 가격을 한 눈으로 볼 수 있는 달력도 있다. 나중에는 욕심이 생겨 해당 요청도 분석하여 기능에 추가했다.

 

[스카이스캐너] 항공권 가격 캘린더

 

오랜 시간 끝에 데이터들의 관계를 파악했고 이를 파싱하는 작업을 진행할 수 있었다.

 

 

음성 플랫폼

 

NUGU Play Builder

 

Play Builder는 NUGU play kit에서 제공하는 개발 툴로 간단한 코드 정의와 예시 문장 입력만으로 손쉽게 대화 기반의 AI 음성 서비스를 만들 수 있다. ( NUGU developers ) NUGU의 음성 서비스를 만들기 위해서는 해당 빌더를 사용해야 한다. 빌더의 가이드 문서를 보면 아래와 같은 그림이 나와있는데 이 그림은 음성 플랫폼이 동작하는 전체적인 과정을 담고 있다.

1. 사용자가 NUGU 스피커를 통해 음성 명령을 내리면 NUGU에서는 해당 음성의 자연어 이해(NLU)를 통해 의도를 파악한다.

2. 파악한 의도에 맞는 액션을 수행하고 응답을 생성한다. (필요하다면 외부 API 등을 활용하는데 이때는 백엔드 연동이 필요하다)

3. 생성된 응답은 음성 합성(TTS)를 통해 사용자에게 전달된다.

 

NUGU 플랫폼은 이처럼 자연어 이해와 음성 합성 부분을 도와준다. 서드파티 개발자들은 이를 활용하여 '파악된 의도를 이용해 적절한 응답을 생성하는 작업'을 하게 된다. 해당 과정은 가이드 문서에 자세히 나와있으며 크게 어렵지 않은 내용이라 개발 지식이 없어도 쉽게 이해할 수 있고 직접 만들어 볼 수도 있다. 이 중에 서드파티가 정의할 부분은 크게 Intent 그리고 Action 으로 나뉜다. 참고로 이러한 음성 플랫폼의 구조는 NUGU뿐만 아니라 카카오, 네이버, 아마존, 구글 등 다른 음성 플랫폼들도 비슷한 구조이다.

 

Intent, Entity, Action

 

Intent, Entity, Action은 음성 서비스를 설계하는데 있어 가장 기본이 되고 뼈대가 되는 중요한 개념이다. 그래서 간략하게 살펴보고 넘어가려고 한다. 예를 들어, 날씨와 관련된 서비스를 만든다고 한다고 했을 때 아래와 같은 2가지 기능이 있다고 해보자.

 

1. 날씨 정보를 알려주는 기능

2. 날씨에 관련된 음식이나 노래를 추천하는 기능

 

그렇다면 각 기능에 대한 사용자들의 예상 발화를 아래처럼 생각해 볼 수 있다.

 

날씨 정보를 알려주는 기능
  • 날씨 알려줘
  • 오늘 날씨 뭐야?
  • 날씨 어때?
날씨에 관련된 음식이나 노래를 추천하는 기능
  • 오는 날에 먹을만한 음식 추천해줘
  • 오는 날에 들을만한 노래 추천해줘

 

이처럼 각 기능에 대한 예상 발화를 구분한 범주를 Intent 라고 한다. ['날씨 알려줘', '오늘 날씨 뭐야?', '날씨 어때?']의 발화들은 '날씨 정보를 알려 달라'는 의도를 가지고 ['비오는 날에 먹을만한 음식 추천해줘', '비오는 날에 들을만한 노래 추천해줘']는 '날씨에 맞는 무언가를 추천해달라'는 의도를 가진다. 그리고 위의 표에 색칠되어 있는 단어는 Entity를 나타낸다. Entity는 Intent만으로 특정 기능의 발화 의도를 표현하기 어려울 때 사용하는 부가 상세 정보를 의미하는 개체이다. 예를 들어, 사용자가 '오늘 날씨 뭐야?' 라고 물으면 '오늘'이라는 키워드를 통해 오늘 날씨에 대한 정보를 알려줄 수 있게 된다. 오늘 뿐만 아니라 '내일 날씨 뭐야?'와 같은 발화에 대해서도 가능하다. 이것은 Entity Type에 따라 다른데 자세한 건 가이드 문서 - Intent와 Entity에 나와있다.

Intent와 Entity

 

각 기능에 대한 Intent 등록이 끝났다면 실제로 Intent를 분석한 내용을 통해 응답을 생성하는 역할을 정의해야 하는데 이 부분은 Action을 통해 이루어진다. 즉, Action은 사용자의 발화에 대해서 적절한 답변을 만드는 역할을 한다.

 

NUGU 가이드 문서 - Action 정의하기

 

항공권 검색 서비스의 Intent 및 응답 프롬포트

 

그렇다면 항공권 검색과 관련된 Intent는 어떤 것들이 있을까? 공모전 당시에는 아래처럼 도 항공권, 왕복 항공권, 항공권 리뷰에 대한 3가지 Intent로 나눴었다. 아래 Intent는 사실 잘못된 설계이다. ask.oneway, ask.round는 같은 의도(항공권 검색)를 가지기 때문에 이 둘은 통합되어 Entity로 구별하는 방법이 더 적절하다.

 

아래는 각 Intent 별 응답 프롬포트이다.

 

 

[편도항공권] 날짜를 모두 발화했을 경우, 월만 발화했을 경우 최저가 응답 프롬포트

 

편도 항공권에 대한 검색 결과이다. 설계시 가장 많이 고민했던 점은 날짜를 정확하게 말하지 않은 경우에 대해 어떻게 처리할 것인가?였다. 고민끝에 '월'은 필수 데이터로 하고 '일'은 있어도 되고 없어도 되는 옵션으로 지정했다. '일'을 발화하지 않은 경우는 오른쪽 스크린샷처럼 해당 '월'에 항공권이 가장 저렴한 날을 안내해준다. 또한 '월', '일'을 모두 발화한 경우에는 해당 월의 평균가보다 얼마나 저렴한지 혹은 얼마나 비싼지에 대한 정보도 같이 제공함으로써 좀 더 알찬 정보를 제공하고자 했다.

 

[왕복항공권] 최저가, 항공권 리뷰

 

왕복 항공권 검색 결과와 특정 월에 대한 항공권 가격 리뷰 결과이다. 왕복 항공권 검색의 경우 출발 월, 일과 도착 월, 일을 모두 필수 데이터로 지정했다. 항공권 리뷰 기능은 위에서 언급했던 달력 형식의 API를 따로 파싱하여 마지막에 추가한 기능이다. 한 달간의 항공권 가격에 대한 최고가, 최저가, 평균가를 알려준다.

 

공모전 PT 및 시상

 

공모전 PT와 시상식은 을지로에 있는 SK텔레콤 건물에서 진행됐다. 우리는 대전에서 일찍 출발해 미리 도착했는데 이미 몇 개 팀들이 모여 있었다. 한 가지 걱정됐던 점은 PT 중간에 시연을 해야 하는데 우리는 개발할 때 Play 빌더에서 제공하는 채팅 방식으로만 테스트를 진행했기 때문에 스피커를 한 번도 사용해 본 적이 없어서 사용방법도 잘 몰랐고 무엇보다 스피커가 말을 잘 알아들을지 걱정이 됐다. 그래서 급하게 시연용 스피커가 있는 곳에서 테스트를 계속 진행했고 최대한 잘 알아듣는 날짜와 도시를 골라서 노트에 적어뒀다. 그러다가 머릿속에 떠오른 것은 심사자분들께 직접 여행지를 추천받아서 검색 결과를 들려주면 그 효과가 더 클 것 같다는 생각이 들었다. 혹시라도 발음이 어려운 이상한 도시를 말하면 어쩌나 하는 걱정도 있었지만 그런 위험부담은 감수하기로 했다.

 

PT는 아이디어 부문이 먼저 진행되고 이어서 개발 부문 PT가 진행됐다. 다른 팀들도 열심히 준비해 온 것 같았고 중간중간 좋은 아이디어도 보였다. 곧 우리 차례가 되어 친구가 발표를 맡아 진행하고 나는 옆에 서서 시연 준비를 했다. 시연할 때는 계획한 대로 심사자분들께 가고 싶은 여행지를 물어봤고 한 분이 '후쿠오카'를 말씀하셨다. 근데 나는 발음을 제대로 못 알아들어서 후쿠호카라고 발음했던 것 같다.. 아무튼 다행히도 검색이 성공적으로 됐고 순간 정적이 흐르며 스피커 소리가 강당에 울려 퍼졌다. 스피커 소리를 최대로 키워놔서 그런지 뭔가 전율이 돋았고 어디에선가 '우와'하는 감탄도 들렸다. 그렇게 발표가 끝나고 시상식을 기다리면서 1등 할 것 같은 좋은 예감이 들었다.

 

 

그리고 결과는 기쁘게도 최우수상을 받았다. 상금이 무려 2천만 원이었는데 실감이 잘 나지 않았고 그냥 기분이 정말 좋았다. 인생에 지금 같은 순간이 또 있을까 싶은 생각이 들었다. (시상식이 끝나고 곧장 친구와 비싼 회를 먹으러 갔던 기억이 난다..) 아무튼 그 다음 날 학교에 수업을 들으러 나갔는데 친구와 나는 도저히 강의에 집중이 안돼서 ㅋㅋ 같이 강의 중간에 복도로 나와서 어제 있었던 일이 꿈만 같고 뭔가 허망하다는 얘기를 했는데 친구도 마찬가지였다. 뭔지 모를 허망함..ㅎ

 

SK True Innovation 입주

 

첫 공모전 이후에 참 많은 일들이 있었지만 주요 사건?만 간략히 정리해 보자면

 

3개월 후에 같은 공모전이 또 올라왔는데 여기에도 출전해서 우수상을 받았다. 이번 공모전은 이전 공모전과 다르게 한 가지 혜택이 있었는데 을지로에 있는 SK True Innovation에 4개월간 입주할 수 있는 혜택이었다. 사실 취업시즌이라 많은 고민이 됐지만 언제 이런 경험을 해보겠냐는 생각과 무엇보다 이 일이 재미있어서 고민 끝에 서울로 짐을 싸서 올라갔다.

 

Play 상용화

 

True Innovation 랩에 가서 가장 먼저 항공권 음성 검색 서비스를 출시하고 싶었지만 데이터 문제 때문에 어쩔 수 없이 2번째 공모전 출품작인 '라스트 브레드'를 상용화 하기 위한 개발을 먼저 시작했다. 그리고 얼마 후에 '라스트 브레드'를 출시하고 곧 이어 퀴즈 서비스인 '오늘의 퀴즈'를 출시했다.

 

 

그 무렵 같은 주제로 3번째 공모전이 올라왔는데 또 나가서 우수상을 받아 True Innovation에 있는 기간이 4개월 연장됐다. (근데 슬슬 눈치가 보여서 마지막 공모전이라고 생각을 했다.ㅋㅋ)

 

예비 창업 패키지

 

True Innovation에 있으면서 따로 돈을 받거나 버는 입장이 아니었기 때문에 국가(중소 벤처기업부)에서 지원하는 예비 창업 패키지에 지원했다. 덕분에 약 6500여만 원을 지원받아 생계를 이어나갈 수 있었다.

 

항공권 DB 제공업체와의 미팅 및 계약 체결

 

어느 날 토파스여행정보라는 업체에서 SKT를 통해 미팅 요청이 왔다. 처음엔 여기가 어딘가 싶었는데 무려 한진 계열사이고 (와우..) 항공권 관련 DB를 제공하는 업체였다. 처음에 이런 곳에서 우리에게 데이터를 제공해 준다고 했을 때 정말 놀랐다. 아무튼 정말 좋은 기회라고 생각했고 행운이 계속해서 따르는 것만 같았다.

 

이후 몇 번의 미팅 속에 전체적인 설계와 항공권 API에 대한 spec이 어느정도 정리가 됐다. 그리고 20년 1월.. 드디어 정식으로 계약을 체결하게 되었다. 우리의 첫 외주 프로젝트였다. 우리는 항공권 데이터가 필요했고 첫 공모전 당시 출품작이었던 항공권 검색 서비스를 꼭 출시하고 싶었다. 우리 자체의 서비스를 만들고 싶었지만 역부족이었기 때문에 이렇게라도 개발할 수 있던 건 최선의 선택이었고 어쨌든 개발을 할 수 있게 돼서 너무 좋았다.

 

대화형 항공권 음성 검색 서비스 상용화

 

정말 긴 시간 끝에 드디어 대화형 항공권 음성 검색 서비스 개발을 끝내고 상용화 중이다. 🎉

개발을 하면서 가장 고민을 많이 했던 점은 각 대화 문맥에 맞는 적절한 대응책이었다. 하지만 이러한 문맥들을 상태들로 관리하자니 생각보다 그 수가 상당히 많아서 로직이 복잡해졌다. 지금 당장은 어찌어찌 개발한다 해도 향후 유지 보수나, 확장성에 어려울 것이고 버그 가능성도 커지기 때문에 많은 고민이 됐다. 여러 고민 끝에 데이터 유무에 따른 상태 변화와 상태에 따른 응답 프롬포트 매핑 방식을 선택해 전체적인 로직을 설계했다. 특히 npm 라이브러리인 javascript-state-machine을 활용하여 이러한 상태 변화를 좀 더 체계적이고 일관되게 관리했다. 그 결과 유지 보수가 더 간편해졌고 상태의 추가, 제거, 수정 등의 작업이 수월해졌다.

 

초기 로직 설계

 

현재 상용화 중인 롯데제이티비와 한진관광 플레이!

 

 

느낀점

 

대학교 졸업을 앞두고 별 생각없이 나갔던 공모전에서 위 프로젝트를 통해 최우수상을 받고 나서, 이후로 이렇게 오랫동안 음성 스피커를 이용해 개발을 할 줄은 생각지도 못했다. 처음에는 재미로 시작했던 일이었지만 여러 서비스를 기획하고 개발하면서, 상용화를 한다는 것에는 신경써야 할 점이 한 두가지가 아니라는 점을 알게 되었다.

 

항공권 음성 검색 서비스를 개발하면서도 많은 문제가 있었다. 개발을 하면서 가장 고민을 많이 했던 점은 수많은 대화 문맥을 어떻게하면 효율적으로 관리할 수 있는가였다. 처음에는 대화 문맥이 몇개 되지 않아서 문맥별 응답 프롬프트를 1:1로 매핑하며 하드코딩 했었다. 하지만 대화 문맥이 추가되고 다양해질수록 점점 코드가 복잡해지기 시작했다. 당장은 어찌저찌 개발한다 해도 향후 유지 보수나 확장성에도 꽝일 것이고, 에러가 나도 발견하기 쉽지 않을 것 같았다.

 

하루 하루 고민을 하며 지내다가 문득 대학교 계산이론 수업 때 배운 "스테이트 머신" 개념이 떠올랐다. 그리고 마침 보고 있던 "봇 설계는 이렇게 한다"는 책에 나온 의도 매핑 방법에도 해당 개념이 등장했다. 여기서 확신을 얻어 관련 라이브러리를 탐색했고 "javascript-state-machine"를 발견했다. 해당 라이브러리를 활용하여 모든 대화 문맥을 상태화했고, 상태 변화에 따른 변이 규칙을 정의했다. 이렇게 하니 문맥을 추가하고 수정, 제거하는 작업이 훨씬 수월해졌다. 결과적으로 유지보수, 확장성, 에러 관리에 큰 도움이 되었다.

 

또 이외에도 항공권 데이터를 실시간으로 받아오는데 문제가 있었다. 항공권 API의 응답 속도가 길게는 3초 이상이 걸리면서 빠른 편이 아니었기 때문에 응답 속도 문제를 해결해야만 했다. 우리는 AI 스피커를 이용하는 써드 파티였기 때문에 모든 것을 백엔드단에서 해결해야 했다. 이와 관련된 회의를 통해 1가지 해결책이 나왔다. 바로, 사용자로 하여금 한번 더 검색을 되묻게 하는 것이었다.

이게 무슨 말이냐하면, 우리가 설계한 대화 흐름은 사용자가 "검색해줘"라는 말을 하면 이와 동시에 항공권을 검색하는 프로세스였다. 따라서 이 때, 항공권 API 응답이 지연되면 "한번 더 검색이라고 말씀해주세요"라는 프롬프트를 내보내며 시간을 버는 방법이다. 그 사이에 백엔드에서는 항공권 API 응답을 저장을 해뒀다가 재요청 시 해당 응답을 내보내면 된다.

 

해당 방법으로 개발을 한 후 nugu에 서비스를 하기 위해 심사 요청을 넣었다. 하지만 통과되지 못했다. 이유는 우리가 우려했던대로 검색 지연 문제였다. 우리가 해결한 방식으로는 사용자 편의성에 좋지 않다는 것이었다. 하긴 우리가 개발하면서도 계속 아쉬웠던 부분이었긴 했다. 어쩔 수 없이 다시 한 번 해결책을 곰곰이 생각하게 되었다. 그렇게해서 2번째 해결책이 나왔다. 바로, 검색 타이밍을 앞당기는 것이었다.

자세히 설명하자면, 우선 기존 해결 방법에서는 사용자가 검색을 요청하는 타이밍과 동시에 항공권을 검색하는 프로세스였다. 하지만 생각해보면 굳이 검색 요청이 들어옴과 동시에 검색을 하지 않아도 되었다. 즉, 검색을 위한 필수 데이터만 모두 채워진다면 미리 항공권을 검색해서 데이터를 가지고 있으면 되는 것이다. 여기서 필수 데이터란 "출발지, 도착지, 출발날짜, 도착날짜"에 관한 데이터이다.

결국 사용자가 검색을 요청하기 전에 백엔드에서 필수 데이터의 유무를 판단하여 미리 항공권 검색을 수행하고, 사용자의 검색 요청에 곧바로 검색 결과를 응답할 수 있게 되었다.

 

항공권 음성 검색 프로젝트를 진행하면서 위 문제들 말고도 자잘한 문제들부터 시작해서 정말 다양한 문제들이 존재했다. 하지만 이를 하나둘씩 해결해나가는 과정을 겪으면서 앞으로 어떤 문제든지 해결할 수 있다는 자신감을 얻었다. 끈기로 시간을 가지고 해결한다면 우리가 부닥친 보통의 문제들은 대게 모두 해결 가능하다는 것을 배웠다. 앞으로 또 어떤 분야에 대해 개발을 하게 될지 내 인생이 어떻게 어디로 흐를지 알 수 없지만, 해당 프로젝트를 통해 배운 점들은 틀림없이 성장의 밑거름이 되었고, 앞으로도 이런 경험들이 펼쳐질 미래가 정말 기대된다.

 

 

🖊️ 원고

음성 인터페이스를 활용한 AI 대화형 항공권 검색 시스템 개발 (15-29p)

항공권 음성 검색 시스템을 설계하고 개발을 진행했던 과정들에 대해 정리하여 원고를 작성했다. 위에서 설명했던 문제를 해결했던 방법들에 대해서도 좀 더 자세히 적혀 있다. 원고를 쓴 이유는 프로젝트를 진행하고 나서 의미 있는 결과물을 남기고 싶은 것이 가장 컸다. 대부분의 경우, 시간이 지나면 프로젝트를 진행했던 기억이 가물가물 해진다. 인생에서 많은 것을 배웠던 프로젝트였던 만큼 꼭 잊지 않고 싶었다.

그리고 더불어서, 혹시라도 이 원고가 우리와 비슷한 서비스를 개발하는 사람들에게 조금이라도 도움이 된다면 그것도 정말 좋겠다.

반응형