[참고] 자연어 질의 시스템 설계 — 역질문으로 구체화하기¶
Text-to-SQL 심화에서 VectorDB + dbt 온톨로지로 SQL을 생성하는 기술을 다뤘습니다. 이 페이지에서는 한 단계 더 나아가, 사용자의 모호한 질문을 역질문으로 구체화하는 시스템을 설계합니다.
1. 문제 — "그래서 뭘 원하는 거야?"¶
이 한 문장에 모호한 부분이 최소 4가지 있습니다:
| 모호한 부분 | 가능한 해석 |
|---|---|
| 이번달 | KST 기준? UTC 기준? 4/1~4/30? 4/1~오늘? |
| 활성 사용자 | DAU 합산? MAU? 로그인 기준? 이벤트 발생 기준? |
| 국가 | 전체? 한국만? 일본 포함? |
| 결과 형태 | 숫자 하나? 일별 추이? 국가별 비교? |
LLM이 이 모호함을 무시하고 아무 해석이나 골라서 SQL을 생성하면, 결과는 나오지만 사용자가 원한 답이 아닐 확률이 높습니다.
모호한 채로 실행하면 생기는 일
3번 왕복. 차라리 처음에 물어보는 게 빠릅니다.2. 해결 — 역질문(Clarification)을 통한 구체화¶
핵심 아이디어¶
LLM이 SQL을 생성하기 전에, 모호한 부분을 감지하고 역질문으로 구체화한다.
사용자: "이번달 활성 사용자 알려줘"
↓
시스템: "다음을 확인할게요:
1. 기간: KST 기준 2026-04-01 ~ 2026-04-30 맞나요?
2. '활성 사용자'는 해당 월에 1회 이상 로그인한
유저 수(MAU)인가요?
3. 전체 국가 기준인가요, 특정 국가만 볼까요?"
↓
사용자: "네 맞아요, 한국만요"
↓
시스템: → SQL 생성 → 실행 → "312,000명입니다"
한 번의 역질문으로 3번의 재실행을 줄입니다.
흐름도¶
┌────────────────────────────────────────────────────┐
│ 사용자 입력: "이번달 활성 사용자 알려줘" │
└───────────────────────┬────────────────────────────┘
↓
┌────────────────────────────────────────────────────┐
│ Step 1. 의도 분석 (Intent Analysis) │
│ │
│ LLM이 질문에서 추출: │
│ - 기간: "이번달" (모호) │
│ - 메트릭: "활성 사용자" (모호 — DAU? MAU?) │
│ - 필터: 없음 (모호 — 국가 미지정) │
│ - 결과 형태: 미지정 │
└───────────────────────┬────────────────────────────┘
↓
┌────────────────────────────────────────────────────┐
│ Step 2. 모호성 감지 (Ambiguity Detection) │
│ │
│ dbt 메타데이터와 대조: │
│ │
│ "활성 사용자" → metrics.yml 검색 │
│ → dau: "해당 일자에 1회 이상 로그인한 유저 수" │
│ → mau: "해당 월에 1회 이상 로그인한 유저 수" │
│ → 2개 매칭 → 모호함 감지! │
│ │
│ "이번달" → 기간 파라미터 │
│ → 서버 타임존 확인 필요 → 모호함 감지! │
│ │
│ 국가 필터 → schema.yml의 country_code │
│ → 허용값: KR, JP, US, TW → 미지정 → 모호함 감지! │
└───────────────────────┬────────────────────────────┘
↓
모호한 부분이 있는가?
╱ ╲
Yes No
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ Step 3a. 역질문 │ │ Step 3b. 바로 SQL │
│ │ │ 생성 & 실행 │
│ 구체화 질문 생성 │ │ │
│ → 사용자 응답 대기 │ │ (모호함 없으면 │
│ → 응답 반영 │ │ 즉시 실행) │
│ → Step 2로 재검증 │ │ │
└──────────┬─────────────┘ └──────────┬───────────┘
↓ ↓
┌────────────────────────────────────────────────────┐
│ Step 4. SQL 생성 (Text-to-SQL) │
│ │
│ VectorDB에서 유사 쿼리 검색 (RA-SQL) │
│ + 구체화된 파라미터로 SQL 생성 │
│ + 자기 검증 후 실행 │
└───────────────────────┬────────────────────────────┘
↓
┌────────────────────────────────────────────────────┐
│ Step 5. 결과 반환 │
│ │
│ "2026년 4월 한국 MAU는 312,000명입니다." │
│ (+ 사용한 조건 명시) │
└────────────────────────────────────────────────────┘
3. 모호성 감지 — dbt 메타데이터가 근거¶
역질문이 "LLM의 감"이 아니라 dbt에 정의된 메타데이터를 근거로 생성되는 것이 핵심입니다.
모호성 유형과 감지 방법¶
| 모호성 유형 | 감지 근거 | 역질문 예시 |
|---|---|---|
| 메트릭이 2개 이상 매칭 | metrics.yml에서 유사 메트릭 검색 | "DAU인가요, MAU인가요?" |
| 기간이 모호 | 질문에 상대적 시간 표현 감지 | "KST 기준 4/1~4/30 맞나요?" |
| 필수 디멘션 누락 | schema.yml의 주요 필터 컬럼 확인 | "전체 국가인가요, 특정 국가만?" |
| 결과 형태 미지정 | 집계 수준이 불명확 | "숫자 하나? 일별 추이? 국가별 비교?" |
| 비즈니스 용어 충돌 | 용어집에서 동일 용어 복수 정의 | "매출은 gross 기준? net 기준?" |
메트릭 매칭 예시¶
# dbt metrics.yml — "매출" 관련 메트릭이 3개
metrics:
- name: gross_revenue
label: "총 매출 (Gross)"
description: "환불/수수료 차감 전 총 결제액"
- name: net_revenue
label: "순 매출 (Net)"
description: "결제액 - 환불 - 수수료. 재무 보고 기준."
- name: arpu
label: "ARPU"
description: "유저당 평균 매출"
사용자: "이번달 매출 알려줘"
시스템 내부:
"매출" → VectorDB 검색 → 3개 메트릭 매칭
→ 모호함 감지!
역질문:
"'매출'에 대해 다음 중 어떤 기준인가요?
1. 총 매출 (gross_revenue) — 환불/수수료 차감 전
2. 순 매출 (net_revenue) — 재무 보고 기준
3. ARPU — 유저당 평균 매출"
모호하지 않으면 바로 실행¶
# metrics.yml — "신규 가입자"는 1개만 정의
metrics:
- name: new_signups
label: "신규 가입자"
description: "해당 일자에 가입 완료한 유저 수"
사용자: "어제 신규 가입자 몇 명이야?"
시스템 내부:
"신규 가입자" → 1개 매칭 (new_signups)
"어제" → 명확한 날짜 (2026-04-11)
국가 필터 → 이 메트릭은 국가별 분리 없음
→ 모호함 없음!
→ 역질문 없이 바로 SQL 생성 & 실행
→ "어제(4/11) 신규 가입자는 1,847명입니다."
핵심 원칙
모호할 때만 물어보고, 명확하면 바로 실행한다. 매번 역질문하면 사용자가 짜증납니다.
4. 역질문 전략 — 똑똑하게 물어보기¶
기본값 제안 (Default Suggestion)¶
열린 질문보다 기본값을 제안하고 확인받는 방식이 사용자 경험에 좋습니다.
# 나쁜 역질문 (열린 질문)
"기간을 지정해주세요."
"어떤 메트릭을 원하시나요?"
# 좋은 역질문 (기본값 제안)
"기간: KST 기준 2026-04-01 ~ 2026-04-30 맞나요?"
"'활성 사용자'는 MAU(월간 로그인 유저 수) 기준이 맞나요?"
한 번에 모아서 질문¶
모호한 점이 여러 개면 한 번에 모아서 질문합니다.
# 나쁜 패턴 (핑퐁)
시스템: "기간이 언제인가요?"
사용자: "이번달"
시스템: "국가는요?"
사용자: "한국"
시스템: "메트릭은요?"
사용자: "..." ← 이쯤 되면 귀찮음
# 좋은 패턴 (한 번에)
시스템: "다음을 확인할게요:
1. 기간: KST 기준 2026-04-01 ~ 2026-04-30 (맞나요?)
2. 메트릭: MAU — 월간 1회 이상 로그인 유저 수 (맞나요?)
3. 국가: 전체 (특정 국가만 볼까요?)
맞으면 '확인', 수정할 부분이 있으면 알려주세요."
사용자: "한국만요" ← 한 번에 끝
학습하는 역질문¶
같은 사용자가 반복적으로 같은 조건을 지정하면, 다음부터 기본값으로 적용합니다.
# 첫 번째 질의
사용자: "이번달 매출"
시스템: "국가는 전체인가요?"
사용자: "한국만"
# 두 번째 질의 (학습된 기본값 적용)
사용자: "지난달 매출"
시스템: "지난달(3월) 한국 순매출 기준으로 조회할까요?"
← 이전 선택을 기본값으로 제안
5. MCP 서버 설계¶
추가할 Tool¶
기존 MCP 서버에 역질문 기능을 추가합니다.
@mcp.tool()
def analyze_question(question: str) -> dict:
"""사용자의 자연어 질문을 분석하고, 모호한 부분을 식별합니다.
반환값에 clarifications가 있으면 사용자에게 역질문이 필요하고,
없으면 바로 SQL을 생성해도 됩니다.
Args:
question: 사용자의 자연어 질문
"""
# 1. 질문에서 의도 추출
intent = extract_intent(question) # 메트릭, 기간, 필터 등
# 2. dbt 메타데이터와 대조하여 모호성 감지
ambiguities = detect_ambiguities(intent)
# 3. 결과 반환
return {
"intent": {
"metrics": intent.metrics, # ["활성 사용자"]
"time_range": intent.time_range, # "이번달"
"filters": intent.filters, # []
},
"clarifications": [
{
"field": a.field,
"reason": a.reason,
"options": a.options,
"default": a.default,
}
for a in ambiguities
],
# clarifications가 비어있으면 → 모호함 없음 → 바로 실행 가능
}
@mcp.tool()
def generate_sql(
question: str,
resolved_params: dict,
) -> dict:
"""구체화된 파라미터로 SQL을 생성합니다.
analyze_question의 역질문이 해소된 후 호출합니다.
Args:
question: 원래 자연어 질문
resolved_params: 구체화된 파라미터
예: {"metric": "mau", "time_range": "2026-04-01~2026-04-30",
"country": "KR", "granularity": "total"}
"""
# 1. VectorDB에서 유사 쿼리 검색 (few-shot)
similar = find_similar_queries(question, top_k=3)
# 2. 관련 스키마 검색
schema = find_relevant_schema(question)
# 3. 메트릭 정의 조회
metric_def = get_metric_definition(resolved_params["metric"])
# 4. LLM에게 SQL 생성 요청 (few-shot + 스키마 + 메트릭 + 파라미터)
sql = llm_generate_sql(
question=question,
params=resolved_params,
similar_queries=similar,
schema_context=schema,
metric_definition=metric_def,
)
# 5. 검증
validation = validate_sql(sql)
return {
"sql": sql,
"validation": validation,
"used_metric": metric_def,
"used_tables": extract_tables(sql),
}
Tool 호출 흐름¶
LLM Agent의 동작:
1. 사용자: "이번달 활성 사용자 알려줘"
2. LLM → analyze_question("이번달 활성 사용자 알려줘")
응답: {
"intent": {"metrics": ["활성 사용자"], "time_range": "이번달"},
"clarifications": [
{"field": "metric", "options": ["dau", "mau"], "default": "mau"},
{"field": "time_range", "default": "2026-04-01~2026-04-30"},
{"field": "country", "options": ["전체","KR","JP","US","TW"]}
]
}
3. LLM → 사용자에게 역질문 (clarifications 기반)
4. 사용자: "MAU 맞고, 한국만"
5. LLM → generate_sql(
question="이번달 활성 사용자 알려줘",
resolved_params={
"metric": "mau",
"time_range": "2026-04-01~2026-04-30",
"country": "KR",
}
)
6. LLM → run_query(sql) → 결과 반환
6. UI 설계 — 질의 페이지¶
BI 도구(Superset 등)에 연동하거나, 독립 웹 페이지로 구현할 수 있습니다.
채팅형 인터페이스¶
┌─────────────────────────────────────────────────┐
│ 📊 데이터 질의 │
├─────────────────────────────────────────────────┤
│ │
│ [사용자] 이번달 활성 사용자 알려줘 │
│ │
│ [시스템] 다음을 확인할게요: │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 기간: 2026-04-01 ~ 04-30 (KST) │ │
│ │ 메트릭: ○ DAU ● MAU │ │
│ │ 국가: [전체 ▾] │ │
│ │ │ │
│ │ [확인] [수정] │ │
│ └─────────────────────────────────┘ │
│ │
│ [사용자] 한국만 볼게요 → [확인] │
│ │
│ [시스템] 2026년 4월 한국 MAU는 312,000명입니다. │
│ │
│ ┌─────────────────────────────────┐ │
│ │ 사용한 조건 │ │
│ │ - 메트릭: MAU (월간 로그인 유저) │ │
│ │ - 기간: 2026-04-01 ~ 04-30 KST │ │
│ │ - 국가: KR │ │
│ │ │ │
│ │ [SQL 보기] [대시보드에서 보기] │ │
│ └─────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ [전송] │
│ │ 질문을 입력하세요... │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
역질문 UI 패턴¶
| 모호성 유형 | UI 컴포넌트 | 예시 |
|---|---|---|
| 메트릭 선택 | 라디오 버튼 | ○ DAU ● MAU |
| 기간 확인 | 날짜 피커 + 확인 버튼 | [2026-04-01] ~ [2026-04-30] |
| 국가 필터 | 드롭다운 | [전체 / KR / JP / US / TW] |
| 결과 형태 | 선택지 | ○ 숫자 하나 ○ 일별 추이 ○ 국가별 비교 |
Superset 연동 방식¶
[독립 질의 페이지]
↓ 사용자 질문 + 역질문 해소
↓
[MCP 서버]
↓ SQL 생성
↓
두 가지 경로:
경로 A: 직접 실행
→ MCP 서버의 run_query Tool로 DB 직접 조회
→ 결과를 질의 페이지에 테이블/차트로 표시
경로 B: Superset 연동
→ Superset SQL Lab API 호출
POST /api/v1/sqllab/execute/
{ "database_id": 1, "sql": "SELECT ..." }
→ 결과를 Superset 차트로 렌더링
→ 또는 Superset 대시보드 링크 생성
7. 구현 로드맵¶
Phase 1: analyze_question 구현¶
Phase 2: 채팅 UI 프로토타입¶
Phase 3: 학습 기능¶
Phase 4: Superset 통합¶
8. 정리¶
이 시스템의 강점은 세 가지가 맞물린다는 점입니다:
| 구성 요소 | 역할 |
|---|---|
| dbt 온톨로지 | 모호성 감지의 근거 — "활성 사용자가 2개 정의되어 있다" |
| VectorDB | SQL 생성의 지식 — "비슷한 질문에 이런 SQL을 썼다" |
| 역질문 루프 | 정확도의 보장 — "사용자가 원한 게 맞는지 확인" |
역질문은 "LLM이 모르니까 물어보는 것"이 아니라, "조직의 메트릭 체계에 모호함이 있으니 명확히 하는 것"입니다. 그 근거가 dbt 코드에 있기 때문에, 역질문의 품질도 코드와 함께 진화합니다.
참고 자료¶
- MISP: Multi-Turn Interactive Text-to-SQL (2023) — 역질문을 통한 Text-to-SQL 정확도 향상 연구
- DIN-SQL / DAIL-SQL 패턴 — VectorDB + few-shot SQL 생성 기법
- MCP로 자체 Semantic Layer API — 기존 MCP 서버 구현
← Text-to-SQL 심화 · MCP로 자체 Semantic Layer API →