QueryGPT는 자연어 질문을 SQL로 변환해 생산성을 높이는 Uber의 생성형 AI 기반 쿼리 생성 시스템입니다. 아키텍처 진화, 평가 방법, 한계와 배운 점을 소개합니다.
SQL은 Uber에서 엔지니어, 운영 관리자, 데이터 사이언티스트가 매일 테라바이트 규모의 데이터에 접근하고 조작하는 데 사용하는 핵심 도구입니다. 이러한 쿼리를 작성하려면 SQL 문법에 대한 탄탄한 이해뿐 아니라 내부 데이터 모델이 비즈니스 개념을 어떻게 표현하는지에 대한 깊은 지식이 필요합니다. QueryGPT는 이 간극을 메워 사용자가 자연어 프롬프트로 SQL 쿼리를 생성할 수 있도록 하여 생산성을 크게 높이는 것을 목표로 합니다.
QueryGPT는 대규모 언어 모델(LLM), 벡터 데이터베이스, 유사도 검색을 활용하여 사용자가 입력한 영어 질문으로부터 복잡한 쿼리를 생성합니다.
이 글은 지난 1년간의 개발 여정과 현재 이 비전이 어느 단계에 와 있는지를 기록합니다.
그림 1: 쿼리 작성 프로세스.
Uber의 데이터 플랫폼은 매월 약 120만 건의 인터랙티브 쿼리를 처리합니다. 가장 큰 사용자 집단 중 하나인 운영 조직은 이 중 약 36%를 차지합니다. 쿼리를 작성하려면 데이터 사전에서 관련 데이터셋을 찾고 에디터에서 쿼리를 작성하는 데 상당한 시간이 소요됩니다. 각 쿼리를 작성하는 데 약 10분이 걸린다고 가정하면, 이 프로세스를 자동화하여 약 3분 만에 신뢰할 수 있는 쿼리를 생성하는 QueryGPT의 도입은 생산성 측면에서 큰 도약입니다.
보수적으로 각 쿼리 작성에 약 10분이 걸린다고 가정할 때, QueryGPT는 이 과정을 자동화하여 약 3분 만에 충분히 신뢰할 수 있는 쿼리를 제공합니다. 이는 Uber의 생산성에 큰 이익을 가져옵니다.

그림 2: Querybuilder 사용량.

그림 3: QueryGPT 영향.
QueryGPT는 2023년 5월 Uber의 Generative AI Hackdays에서 제안으로 시작되었습니다. 그 이후 핵심 알고리즘을 반복적으로 개선하여 개념에서 프로덕션 준비가 된 서비스로 발전시켰습니다. 아래에서는 QueryGPT의 진화와 현재 아키텍처를 설명하며, 주요 개선 사항을 강조합니다.
아래는 현재 버전의 QueryGPT에 대한 설명입니다. 참고로, 여기 설명된 두 버전 사이에도 20회 이상의 알고리즘 반복이 있었으며, 모두를 나열해 설명한다면 이 블로그 글의 길이는 아인 랜드의 Atlas Shrugged 보다 길어질 것입니다.
그림 4: QueryGPT Hackdayz 버전.
첫 번째 버전의 QueryGPT는 간단한 RAG를 사용하여 LLM의 쿼리 생성 호출에 포함해야 하는 관련 샘플을 가져왔습니다(Few Shot Prompting). 사용자의 자연어 프롬프트를 벡터화한 후 SQL 샘플과 스키마에 대해 유사도 검색(k-NN)을 수행해 관련 테이블 3개와 관련 SQL 샘플 7개를 가져왔습니다.
이 버전에서는 1등급(tier 1) 테이블 7개와 SQL 쿼리 20개를 샘플 데이터셋으로 사용했습니다. SQL 쿼리는 제공된 테이블 스키마를 어떻게 사용할지에 대한 가이드를 LLM에 제공하고, 테이블 스키마는 해당 테이블에 존재하는 컬럼 정보를 LLM에 제공합니다.
예를 들어 1등급 테이블인 uber.trips_data(실제 테이블이 아님)의 스키마는 다음과 같습니다:
그림 5: uber.trips_data 테이블.
LLM이 내부 Uber 용어를 이해하고 Uber 데이터셋과 잘 작동하도록, LLM 호출에 맞춤 지침도 포함했습니다. 아래는 날짜를 다루는 방식에 대해 LLM이 따르길 원했던 지침의 일부입니다:
그림 6: Uber 데이터셋의 날짜 처리에 대한 맞춤 지침.
관련 스키마 샘플, SQL 샘플, 사용자의 자연어 프롬프트, Uber 비즈니스 지침을 모두 시스템 프롬프트에 감싸 LLM에 요청을 보냈습니다.
응답에는 “SQL 쿼리”와 LLM이 쿼리를 어떻게 생성했는지에 대한 “설명”이 포함되었습니다:
그림 7: 생성된 SQL과 설명.
이 버전의 알고리즘은 소수의 스키마와 SQL 샘플에 대해서는 잘 작동했지만, 더 많은 테이블과 관련 SQL 샘플을 온보딩하면서 생성된 쿼리의 정확도가 떨어지는 현상이 나타났습니다.
사용자의 자연어 프롬프트(“시애틀에서 어제 완료된 트립 수를 찾아줘”)에 대해 스키마 샘플(CREATE TABLE …)과 SQL 쿼리(SELECT a, b, c FROM uber.foo …)에 단순 유사도 검색을 수행하면 관련성이 높은 결과가 잘 나오지 않았습니다.
또 다른 문제는 사용자의 자연어 프롬프트에서 관련 스키마를 찾는 일이 매우 어렵다는 점이었습니다. 사용자 프롬프트를 관련 스키마와 SQL 샘플에 매핑되는 “의도(intent)”로 분류하는 중간 단계가 필요했습니다.
Uber의 일부 1등급 테이블에는 200개가 넘는 컬럼을 가진 매우 큰 스키마가 있습니다. 이러한 대형 테이블은 요청 객체에서 4만~6만 토큰을 차지할 수 있습니다. 이러한 테이블이 3개 이상 포함되면, 당시 가장 큰 모델이 32K 토큰만 지원했기 때문에 LLM 호출이 실패하곤 했습니다.
아래 다이어그램은 현재 프로덕션에서 실행 중인 QueryGPT의 설계를 보여줍니다. 현재 버전은 첫 번째 버전에서 많은 반복적 변경을 포함합니다.
그림 8: QueryGPT(현재).
현재 설계에서는 광고, 모빌리티, 코어 서비스와 같은 특정 비즈니스 도메인에 맞춰 선별된 SQL 샘플과 테이블 모음인 “워크스페이스”를 도입했습니다. 워크스페이스는 LLM의 초점을 좁혀 생성되는 쿼리의 관련성과 정확도를 높입니다.
Uber 내부에서 일반적인 비즈니스 도메인을 식별해 백엔드에 “시스템 워크스페이스”로 생성했습니다. 모빌리티는 쿼리 생성에 활용할 수 있는 기반 도메인으로 식별한 시스템 워크스페이스 중 하나입니다.
Mobility : 트립, 드라이버, 문서 상세 등과 관련된 쿼리
어제 시애틀에서 테슬라 차량으로 완료된 트립의 수를 찾는 쿼리를 작성하시오
이와 함께 “Core Services(코어 서비스)”, “Platform Engineering(플랫폼 엔지니어링)”, “IT”, “Ads(광고)” 등을 포함하여 총 11개의 다른 시스템 워크스페이스도 제공했습니다.
또한 기존 시스템 워크스페이스가 요구 사항에 맞지 않을 경우 사용자가 “맞춤 워크스페이스”를 생성하고 이를 쿼리 생성에 사용할 수 있는 기능도 포함했습니다.
이제 사용자의 모든 프롬프트는 먼저 “의도(intent)” 에이전트를 거칩니다. 이 의도 에이전트의 목적은 사용자의 질문을 하나 이상의 비즈니스 도메인/워크스페이스(확장하면 해당 도메인에 매핑된 SQL 샘플과 테이블 집합)에 매핑하는 것입니다. 우리는 LLM 호출을 사용해 사용자 질문에서 의도를 추론하고 이를 “시스템” 워크스페이스 또는 “맞춤” 워크스페이스에 매핑합니다.
비즈니스 도메인을 선택하면 RAG의 검색 반경을 크게 좁힐 수 있었습니다.
일부 사용자에게서 쿼리 생성에 사용된 테이블이 정확하지 않다는 피드백을 받아, 사용자가 쿼리 생성에 사용될 테이블을 선택할 수 있도록 했습니다.
이를 해결하기 위해 올바른 테이블을 선택해 사용자에게 “확인(ACK)”하거나 목록을 수정할 수 있도록 보내주는 또 다른 LLM 에이전트(테이블 에이전트)를 추가했습니다. 사용자가 보게 될 화면은 아래와 같습니다:
그림 9: 테이블 에이전트.
사용자는 “좋아 보입니다(Looks Good)” 버튼을 누르거나 기존 목록을 편집하여 LLM이 쿼리 생성에 사용할 테이블 목록을 수정할 수 있습니다.
QueryGPT를 더 넓은 사용자층에 롤아웃한 뒤 마주한 흥미로운 문제는, 일부 요청에서 쿼리 생성 중 “간헐적인” 토큰 크기 문제였습니다. 우리는 128K 토큰 제한을 가진 OpenAI GPT-4 Turbo 모델(1106)을 사용했지만, 요청에 많은 토큰을 소모하는 테이블이 하나 이상 포함되면 여전히 토큰 한도 문제가 발생했습니다.
이를 해결하기 위해 LLM 호출을 사용해 LLM에 제공할 스키마에서 관련 없는 컬럼을 제거하는 “컬럼 프루닝(Column Prune)” 에이전트를 구현했습니다.
에이전트의 출력은 다음과 같습니다:
그림 10: 컬럼 프루닝 에이전트.
출력에는 쿼리 생성을 위해 필요한 각 스키마의 슬림한 버전이 포함됩니다. 이 변경은 토큰 크기와 이에 따른 LLM 호출 비용을 크게 줄였을 뿐 아니라 입력 크기가 훨씬 작아져 대기 시간도 줄였습니다.
현재 설계에서도 출력 구조는 변경하지 않았습니다. 응답에는 그림 7과 유사하게 LLM이 생성한 SQL 쿼리와 쿼리가 어떻게 생성되었는지에 대한 설명이 포함됩니다.
QueryGPT 성능의 점진적 향상을 추적하기 위해 표준화된 평가 절차가 필요했습니다. 이를 통해 서비스의 반복되는 문제와 비정상적인 문제를 구분하고, 알고리즘 변경이 총체적으로 성능을 점진적으로 개선하는지 확인할 수 있었습니다.
평가를 위한 황금 질문-SQL 답변 매핑 세트를 큐레이션하려면 초기의 수작업 투자가 필요했습니다. 우리는 QueryGPT 로그에서 실제 질문 세트를 식별하고, 올바른 의도, 질문에 답하는 데 필요한 스키마, 황금 SQL을 수동으로 검증했습니다. 질문 세트는 다양한 데이터셋과 비즈니스 도메인을 포괄합니다.
프로덕션 및 스테이징 환경에서 다양한 제품 플로우를 사용해 쿼리 생성 과정 전반의 신호를 캡처할 수 있는 유연한 절차를 개발했습니다:
제품 플로우목적절차 Vanilla QueryGPT의 기준 성능을 측정합니다.질문을 입력합니다.QueryGPT가 질문에 답하는 데 필요한 의도와 데이터셋을 추론합니다.추론된 데이터셋과 의도를 사용해 SQL을 생성합니다.의도, 데이터셋, SQL을 평가합니다. Decoupled 인간 개입(human-in-the-loop) 경험에서의 QueryGPT 성능을 측정합니다. 초기 결과의 성능에 대한 의존성을 제거해 구성요소 단위 평가를 가능하게 합니다.질문, 의도, 필요한 데이터셋을 함께 입력합니다.QueryGPT가 의도와 데이터셋을 추론합니다.실제(추론이 아닌) 의도와 데이터셋을 사용해 SQL을 생성합니다.의도, 데이터셋, SQL을 평가합니다.
각 평가 질문에 대해 다음 신호를 수집합니다:
시간 경과에 따라 시각화하여 회귀를 식별하고 개선이 필요한 영역을 드러내는 패턴을 찾습니다.
아래 그림은 질문 단위 실행 결과의 예시로, 개별 질문 수준에서 반복되는 단점을 확인할 수 있게 해줍니다.
그림 11A: SQL 쿼리 평가.
그림 11B: 환경별 SQL 쿼리 평가.
각 질문에 대해 생성된 SQL, 오류 원인, 관련 성능 지표를 확인할 수 있습니다. 아래는 WHERE 절에서 파티션 필터를 적용하지 않아 정기적으로 실패하는 질문의 예시입니다. 하지만 정성적 LLM 기반 평가에 따르면, 생성된 SQL은 그 외에는 황금 SQL과 유사합니다.
그림 12: SQL 평가 통계.
또한 각 평가 실행에 대해 정확도와 지연 시간 지표를 집계하여 시간 경과에 따른 성능을 추적합니다.
그림 13: SQL 평가 기준.
LLM의 비결정적 특성상, 기본 QueryGPT 서비스에 변경이 없더라도 동일한 평가를 실행하면 서로 다른 결과가 나올 수 있습니다. 일반적으로 대부분의 지표에서 약 ±5% 수준의 실행 간 변화에 기반해 결정을 과도하게 내리지는 않습니다. 대신 더 긴 기간에 걸쳐 나타나는 오류 패턴을 식별하고, 이를 특정 기능 개선으로 해결합니다.
Uber에는 문서화 수준이 다양한 수십만 개의 데이터셋이 존재합니다. 따라서 평가 질문 세트가 사용자가 던질 수 있는 모든 비즈니스 질문의 우주를 완전히 포괄하는 것은 불가능합니다. 대신 현재 제품 사용을 대표하는 질문 세트를 큐레이션했습니다. 정확도가 향상되고 새로운 버그가 나타남에 따라 평가 세트는 제품의 방향성을 포착하도록 진화할 것입니다.
항상 정답이 하나인 것은 아닙니다. 동일한 질문이라도 다른 테이블을 조회하거나 다른 스타일의 쿼리로 답할 수 있습니다. 황금 SQL과 반환된 SQL을 시각화하고 LLM 기반 평가 점수를 활용하면, 생성된 쿼리가 다른 스타일로 작성되었지만 황금 SQL과 유사한 의도를 갖는지 판단할 수 있습니다.
지난 1년간 GPT와 LLM 같은 신생 기술을 다루며, 에이전트와 LLM이 데이터를 사용해 사용자 질문에 대한 응답을 생성하는 방식의 다양한 미묘함을 실험하고 학습할 수 있었습니다. 그 여정에서 얻은 교훈을 간략히 정리하면 다음과 같습니다:
사용자의 자연어 프롬프트를 RAG에 더 나은 신호로 분해하기 위해 QueryGPT에서 사용한 중간 에이전트들은 첫 버전에 비해 정확도를 크게 개선했습니다. 그 이유 중 상당 부분은 LLM이 작은 단위의 특화된 작업을 부여받았을 때 매우 잘 작동했기 때문입니다.
의도 에이전트, 테이블 에이전트, 컬럼 프루닝 에이전트는 광범위하고 일반화된 작업이 아닌 단일 작업 단위를 수행하도록 요청되었기 때문에 탁월한 성능을 보였습니다.
이는 우리가 지속적으로 개선하고 있는 영역입니다. 요약하면, LLM이 존재하지 않는 테이블을 사용하거나 해당 테이블에 존재하지 않는 컬럼으로 쿼리를 생성하는 경우가 있습니다.
우리는 할루시네이션을 줄이기 위한 프롬프트 실험을 진행하고, 사용자가 생성된 쿼리를 반복 개선할 수 있는 채팅 스타일 모드를 도입했으며, 재귀적으로 할루시네이션을 수정하려 시도하는 “검증(Validation)” 에이전트 도입도 검토 중이지만, 아직 완전히 해결하지는 못했습니다.
QueryGPT에 입력되는 질문은 적절한 키워드로 검색 반경을 좁힌 매우 상세한 질문부터, 오타가 섞인 5단어짜리 질문까지 다양했습니다. 후자의 경우 여러 테이블 간 조인이 필요한 광범위한 질문이기도 합니다.
사용자 질문만을 LLM에 대한 “좋은” 입력으로 전적으로 의존하면 정확도와 신뢰성에 문제가 생겼습니다. LLM에 보내기 전에 사용자 질문을 더 문맥이 풍부한 질문으로 ‘다듬는’ “프롬프트 보강(prompt enhancer/expander)”이 필요했습니다.
이번 버전의 QueryGPT가 다양한 페르소나에게 유용하긴 하지만, 많은 사용자들은 생성된 쿼리가 매우 정확하고 “그냥 동작”하길 기대합니다. 기대치는 높습니다!
우리의 경험상, 이러한 제품을 구축할 때는 초기 사용자층으로 적합한 페르소나를 타겟팅하고 테스트하는 것이 최선이었습니다.
Uber에서의 QueryGPT 개발은 자연어 프롬프트로부터 SQL 쿼리를 생성하는 생산성과 효율성을 크게 향상시킨 변혁적 여정이었습니다. 고도화된 생성형 AI 모델을 활용하는 QueryGPT는 Uber의 방대한 데이터 생태계와 매끄럽게 통합되어, 쿼리 작성 시간을 단축하고 정확도를 개선하며, 데이터의 규모와 복잡성 모두를 다루는 요구에 부응합니다.
거대한 스키마 처리와 할루시네이션 감소 같은 과제가 남아 있지만, 반복적 접근과 지속적인 학습을 통해 꾸준한 개선을 이뤄왔습니다. QueryGPT는 데이터 접근을 단순화할 뿐 아니라 민주화하여, Uber 내 다양한 팀이 강력한 데이터 인사이트에 더 쉽게 접근할 수 있도록 합니다.
운영과 지원 일부 팀을 대상으로 제한적으로 공개한 결과, 일일 활성 사용자 수는 평균 약 300명이며, 약 78%의 사용자가 생성된 쿼리가 처음부터 직접 작성하는 데 들였을 시간을 줄여줬다고 답했습니다.
앞으로도 더 정교한 AI 기술의 통합과 사용자 피드백을 통해 지속적인 향상을 이루어, QueryGPT가 우리의 데이터 플랫폼에서 필수 도구로 남도록 하겠습니다.
QueryGPT는 Uber 전반의 다양한 분야가 협업한 결과물로, 엔지니어링, 제품 관리, 운영에서 일하는 동료들의 전문성과 도메인 지식이 필요했습니다. Abhi Khune, Callie Busch, Jeffrey Johnson, Pradeep Chakka, Saketh Chintapalli, Adarsh Nagesh, Gaurav Paul, Ben Carroll의 훌륭한 기여가 없었다면 오늘의 제품은 존재하지 않았을 것입니다.