안녕하세요, 케이뱅크 AX팀에서 AI/ML Engineer로 일하고 있는 안상규입니다. 요즘은 AI로 반복 업무를 덜고, 혼자서는 하기 어려웠던 일을 할 수 있게 돕는 일을 하고 있습니다. 동료들이 데이터를 더 쉽게 쓰도록 만드는 것도 그중 하나예요.
이런 회의, 한 번쯤 겪어 보셨을 겁니다. 각자 지표를 뽑아 왔는데 화면에 띄운 숫자가 서로 다릅니다. 그러면 본론에 들어가기 전에 어떤 기준으로 쿼리를 짰는지부터 확인해야 합니다. 쿼리를 열어 보면 테이블이 다르거나, 조건 하나가 빠졌거나, 기간을 잡는 기준이 달라요. 한참 걸려 맞춰도 그 결과가 공유되지 않으니, 다음 주에 누군가 같은 지표를 뽑으면 쿼리 확인부터 다시 시작합니다.
그래서 일단, 저희가 해야 할 목표를 두 가지로 정의해봤습니다.
- 1. 흩어진 쿼리를 하나로 통일하는 것
- 2. SQL을 몰라도 자연어로 필요한 쿼리를 찾을 수 있게 만드는 것
이렇게 탄생한 K-Metrics는 지표나 원하는 고객군을 자연어로 물으면, 누군가 이미 검증해 올려 둔 쿼리를 찾아 주고, 없으면 새로 만들어 주는 서비스입니다.
이번 포스팅에서는 ‘검색’ 기능에 대해서 이야기 해보려합니다.
숫자는 매번 다르고, 답을 받으려면 사람을 기다려야 하고
아까 앞에서 제가 알려드린 문제 상황, 기억하시죠? 정리하면 두 가지라 할 수 있는데요.
첫 번째는 숫자가 다른 겁니다.
MAU나 DAU처럼 누구나 보는 지표도 팀마다, 심지어 같은 팀 안에서도 사람마다 다른 테이블에서 뽑고 있으니, 명확한 기준이 없이 각자 익숙한 방식으로 데이터를 뽑게 될 수밖에 없습니다.
두 번째는 기다림입니다.
SQL이 익숙하지 않은 분은 데이터를 직접 뽑을 수 없어서 다른 사람에게 부탁해야 합니다. 그런데 누구에게 물어야 할지 알아보는 것부터 일이에요. 담당자를 찾아 요청하고, 상대가 데이터를 뽑아줄 때까지 기다려야만 하죠.
두 문제 모두 검증된 쿼리가 공유 자산으로 존재하지 않는다는 것에서 시작되었다 생각했습니다.
누군가 이미 특정 쿼리를 짜고 검증까지 했는데도, 그 쿼리가 개인 PC의 파일이나 머릿속에만 있으면 다른 사람은 처음부터 다시 만들거나 그 사람을 찾아가야 합니다.
그런데 문제는 이뿐만이 아닙니다.
금융 회사, 특히 은행은 망 분리 정책에 따라 실제 고객 데이터가 있는 망과 직원들이 업무를 보는 망을 분리해 두어야 하는데요. 이중 사내 인트라넷처럼 임직원들이 사용하는 사내 서비스, 은행이 보유하고 있는 DB는 철저하고 엄격하게 사내망에만 존재해야 합니다. SQL을 작성해주는 에이전트는 있을 수 있어도, 그 SQL을 사내 시스템에서 실행하거나 실제 DB를 대상으로 돌릴 순 없습니다. 그림으로 표현하면 아래와 같습니다.
이렇듯 일반적인 text2SQL과 달리, 스스로 검산할 수 없는 K-Metrics는 틀린 쿼리를 그대로 사용자에게 보여 줄 위험성이 필연적으로 존재합니다.
일단 믿고 쓸 수 있는 서비스가 되려면 무엇을 지켜야 하는지 세 가지 조건으로 정리했습니다.
- 1. 같은 지표를 물으면 항상 같은 쿼리를 줄 것
- 2. 실행 결과 없이도 믿을 수 있는 근거를 보여 줄 것
- 3. 맞는 쿼리가 없으면 없다고 말할 것
사용자 중에는 쿼리에 익숙하지 않은 비개발자가 더 많을 수도 있습니다. 그래서 엉뚱한 쿼리를 자신 있게 내미는 상황을 가장 위험한 상황이라 판단했습니다.
지표를 통일하는 네 가지 방법
위의 세 조건을 기반으로, 네 가지 방향을 차례로 검토했습니다.
| 방법 | 통일 대상 | 한계점 | |
|---|---|---|---|
| ① | 표준 쿼리 저장소만 두기 | 쿼리 파일 | 찾기 어려우면 안 쓰게 되고, 기간·조건은 매번 직접 수정 |
| ② | 대시보드로 계산 결과 모으기 | 계산 결과 | 조회 권한·망 협의가 먼저 필요 |
| ③ | 자연어를 SQL로 생성 | 없음 — 매번 새 SQL | 검증되지 않은 SQL이 나옴 |
| ④ | 검증 쿼리 자산화 + 자연어 검색 | 검증된 쿼리 | ← 선택 |
2번의 경우 대시보드로 결과가 나오기 때문에 데이터를 쉽게 해석할 수 있다는 장점은 있습니다. 하지만 데이터의 조회 권한 문제가 있기에 실현하는데 제약이 있습니다. 3번의 경우 등록된 쿼리가 없어도 답할 수 있다는 장점은 있지만, 매번 다른 SQL이 나와서 첫 번째 조건과 맞지 않았습니다.
결국엔 ④번 방향을 골랐습니다. 지표 정의나 계산 결과가 아니라 검증된 쿼리를 통일의 기준으로 삼고, 쿼리를 찾고 조건을 채우는 단계에 LLM을 활용했습니다.
검색이 동작하는 원리?
먼저 예시와 함께 전체 흐름을 보여 드릴게요.
구어체로 된 질문을 던지면(검색), ①에서 표현을 다듬고(“플박”→”플러스박스”), ②에서는 뜻이 비슷한 후보를 추린 뒤 LLM이 점수를 매깁니다.(합격선 정하기) ③에서는 필수 조건들을 LLM이 직접 채웁니다. 이렇게 품질이 확보된 SQL을 출력하고, 만약 2번에서 모든 후보들이 일정한 수준을 넘지 못 한다면 SQL 생성 단계로 넘어갑니다.
이 흐름에서 검색에 대한 결과의 품질은 두 가지로 결정됩니다.
- 찾을 쿼리가 등록돼 있는지
- 찾은 후보 중 틀린 것을 걸러 내는지
쿼리가 많아질수록 검색만으로 답을 얻기가 쉬워지고, SQL을 새로 만들 필요가 없으니 토큰 비용과 사용자의 대기 시간도 함께 줄어들 거라고 봤습니다. 하지만 무작정 쿼리 수만 늘린다고 결과의 품질은 무조건 올라가지 않습니다.
단어 하나 차이였을 뿐인데…
같은 내용이어도 의문문인지, 평서문인지에 따라 달랐습니다.
“플박”은 케이뱅크 파킹통장인 “플러스박스” 상품을 줄여 부르는 말입니다. 쿼리 설명글에는 공식 이름인 “플러스박스”가 들어가 있지만, 사용자는 줄임말을 쓰는 경우가 많죠. 그래서 뜻이 같아도 단어가 정확하게 일치하지 않으면 맞는 쿼리가 있더라도 틀린 것으로 간주됩니다.
그래서 사내에서 통용되는 용어를 용어 사전을 만들어 정리하고, 의문문과 평서문 모두 같은 사전을 쓰게 했습니다. “플박”, “마동(마케팅 수신 동의)”, “아담대(아파트담보대출)” 같은 줄임말을 사용하더라도, 이 사전에서 줄임말의 정식 이름을 찾아 바꾸기 때문에 검색 방식이 질문이든 평서문이든 같은 단어를 쓰게 됩니다. LLM에 어떤 입력을 주느냐가 결과를 좌우하는 만큼, 참고 데이터의 품질부터 다듬어야 했습니다.
검증된 쿼리는 어떻게 쌓이나
아까 말씀드린 것처럼, K-Metrics는 실제 DB를 조회할 수 없습니다. 그러니 쿼리가 맞는 숫자를 내는지 스스로 확인할 수 없죠. 그래서 쿼리에 대한 검증은 사람이 해야 합니다. 각 팀이 실제 업무에 자주, 주기적으로 사용한 쿼리를 검증된 쿼리로 정하고, 해당 쿼리를 등록할 때 팀명을 함께 표시합니다. 쿼리에 궁금한 점이 있으면 화면에 표시된 담당 팀에 바로 물어보면 됩니다.
문제는 이 쿼리를 K-Metrics 등록 양식(SQL + YAML)으로 옮기는 일이었습니다. 매번 파일을 직접 채울 수는 없어서, SQL과 제목, 설명만 붙여 넣으면 LLM이 참조 테이블, 조건 변수, 키워드를 뽑아 초안을 채우게 했습니다. 저장은 사람이 검수한 뒤에만 됩니다. 등록이 쉬워야 좋은 쿼리가 많아질 테니까요.
이렇게 모인 쿼리는 나중에 SQL을 생성할 때 참고하는 “잘 정리된 문서” 역할도 할 수 있습니다.
자신 있게 오답 내놓기
검색은 질문에 맞는 여러 후보 쿼리 중 합격선을 넘은 것만 보여 줍니다. 그래서 맞는 쿼리가 있을 때 잘 골라내는 것만큼, 맞는 쿼리가 없을 때 후보를 전부 떨어뜨리는 것도 중요합니다. 이 ‘거절하는 능력’을 재기 위해 실험을 설계했습니다.
등록된 쿼리로는 답할 수 없는 질문 12개를 만들어 각각 3회씩 돌려 봤습니다. 케이뱅크에 없는 상품이나 개념을 묻거나, 등록된 쿼리와 분야는 같지만 다른 지표를 묻는 질문들입니다.
결과는 썩 좋지 않았습니다. 12개 중 7개가 합격선을 넘었습니다. 정답이 없는 질문에 대해서도 자신 있게 쿼리를 내밀고 있었던 겁니다. 이대로 뒀다면 사용자는 그럴듯해 보이는 지표를 정답으로 믿고 썼을 겁니다.
| 정답이 없는 질문 | 검색 결과 | Before | After |
|---|---|---|---|
| 하루 주식 거래 내역 | 일별 입출금 금액 총합 | 0.85 | 0.10 |
| 체크카드 발급 수수료 | 고객별 체크카드 가입 건수 | 0.85 | 0.00 |
| 연체 고객 비율 | 월별 MAU | 0.80 | 0.05 |
| 보험 계약 건수 | 총 예금 잔액 | 0.78 | 0.02 |
| 지점별 방문 횟수 | 앱 로그인 횟수 | 0.76 | 0.03 |
| 대출 연체율 추이 | 일별 입출금 금액 총합 | 0.75 | 0.00 |
| 카드 발급 현황 | 고객별 체크카드 가입 건수 | 0.70 이상 | 0.70 |
| (정답이 있는 질문) | (정답 쿼리) | 0.90~1.00 | 0.90~1.00 유지 |
합격선을 상향 조정하면 된다고 쉽게 생각할 수 있겠죠? 그렇지만 실행할 때마다 답변 오차 범위는 ±0.1 정도였습니다. 합격에 대한 기준을 높여도, 돌릴 때마다 판정이 뒤집히죠.
근본 원인은 LLM에 채점을 맡기는 지시문(프롬프트)에 있었습니다. 채점 기준이 “주제가 얼마나 겹치는가”여서, 금융권에 관련된 질문에 대한 쿼리를 합격으로 처리하는 경우가 많았습니다. 케이뱅크에는 지점이 없는데도 “지점별 방문 횟수”라는 질문에 “앱 로그인 횟수” 쿼리를 0.76점으로 내민 식이죠. 그래서 기준을 “이 쿼리가 요청한 지표를 직접 계산하는가”로 바꾸고, 같은 분야라는 이유만으로는 점수를 주지 말라고 지시했습니다. 그 결과 정답이 있는 질문과 없는 질문의 점수 차이가 약 0.8까지 벌어져, 합격선으로 둘을 확실히 구분할 수 있게 됐습니다.
SQL을 찾는 것까지는 했는데 읽을 수가 없었어요
이렇게 검색 기능을 개선했더니, 이번에는 새로운 문제가 생겼습니다. 받은 SQL을 읽기가 어렵다는 것이었습니다.
평소 SQL이 속한 주제를 다뤄 본 사람이 아니라면, 한번에 이해하기 어렵습니다. 이런 상황에서 한 동료가 다른 관점을 알려 주셨습니다.
쿼리를 찾아 주는 데서 그치지 않고, 이걸 해석해주는 기능 역시 핵심이었던 겁니다. 그래서 세 가지를 더했습니다.
첫째, SQL 왼쪽 여백에 테이블, 컬럼, 코드값이 각각 어떤 의미인지를 좌측의 라벨 형태로 보여 줘서 누구나 조건을 쉽게 읽을 수 있게 했습니다. 둘째, 쿼리 상단 주석에 등록한 팀을 표시해 필요하면 바로 연락할 수 있게 했습니다. 셋째, 쿼리마다 어떤 기준으로 어떤 값을 반환하는지 자연어 설명을 붙였습니다.
그래서, 어떤 문제를 해결했을까요?
처음의 두 가지 문제점, 기억하시나요?
해결한 것
조회하고 싶은 지표가 등록이 되어 있다면, 이제 뽑아주는 사람을 찾거나 기다리지 않아도 됩니다. 필요한 사람이 능동적으로 찾을 수 있게 되었거든요. 그냥 질문을 입력하고, K-Metrics가 찾아 준 쿼리를 확인한 뒤 기간만 넣으면 바로 쓸 수 있는 SQL을 받습니다. 게다가 같은 지표가 사람마다 다른 숫자로 나오는 일도 없습니다. 누구든 같은 쿼리를 받게 되니까요.
해결해야 할 문제들
검색에는 분명한 한계가 있습니다. 검색이 돌려줄 수 있는 건 이미 등록된 쿼리뿐이라, 등록된 적 없는 질문은 검색을 아무리 잘 만들어도 답할 수 없습니다. 이런 질문은 검색이 “못 찾았어요”라고 답한 뒤 SQL 생성 단계로 넘어갑니다. (이 부분은 2부에서 다루겠습니다.)
만들면서 배운 세 가지
정답이라기보다는 저희가 직접 부딪히며 얻은 교훈입니다.
1. 지표 통일의 핵심은 답에 이르는 과정을 통일하는 것
‘이 지표는 이겁니다.’ 라고 정의하고 문서로 합의한다 하더라도, 지표를 도출하기까지의 논리가 합의되지 않는다면 합당한 지표라 볼 수 없습니다. 답(숫자)이 아니라 답에 이르는 과정(쿼리)을 통일하는 것이 이번 프로젝트의 핵심이었던 듯합니다.
2. 반드시 필요한, 눈에 보이는 근거
1번의 연장선입니다. 쿼리가 맞다는 건 기획자의 판단일 뿐, 사용자는 근거가 보이지 않으면 믿지 않습니다. 어떤 조건인지, 누가 등록했는지, 어떤 값을 반환하는지까지 알 수 있어야 신뢰도 높은 결과라 할 수 있습니다.
3. 오답을 정답이라 주장하는 현상 막기
환경 상 오답을 발견하는 것이 어려웠었죠. 그래서 K-Metrics는 맞는 것을 찾는 능력만큼 틀린 것을 걸러 내는 능력이 중요했고, 이를 따로 평가하도록 설계되었습니다.
검색했는데 “못 찾았어요”라는 답변이 나온 경우의 SQL 생성은 2부에 이어서 소개하겠습니다.
읽어 주셔서 감사합니다.