안녕하세요, 데이블 AI팀에서 데이터 엔지니어를 맡고 있는 이서림입니다.
"지표에 대한 데이터를 확인하고 싶은데 SQL을 잘 몰라서 직접 작성하기가 어렵네…",
"대충 원하는 건 있는데, 어떤 테이블을 봐야 할지 잘 모르겠어…"
데이터 기반으로 일하고 싶어도, 막상 데이터를 직접 조회하려고 하면 막히는 경험, 한 번쯤 있으시지 않나요?
저도 데이터 조회 요청을 자주 받다 보니 자연스럽게 이런 생각을 하게 됐습니다.
"이제는 SQL을 잘 아는 사람만 데이터를 볼 수 있는 시대는 지나지 않았을까?"
이 글에서는 이런 고민에서 시작된 사내 SQL Agent, DableTalk(데불톡)의 개발 과정과 조직 내 정착 경험을 공유하려고 합니다.
1. 문제: "SQL 쿼리 요청"이 쌓일수록 생기는 일들
데이블에서는 다양한 직군의 구성원들이 매일 데이터를 확인합니다.
하지만 SQL에 익숙하지 않은 분들은 간단한 조회 하나도 누군가에게 부탁해야 했습니다.
•
"○○ 캠페인 CTR 비교해 보고 싶어요. SQL 좀 작성해주실 수 있을까요?"
•
"이 이벤트 이후 유입이 늘었는지 보고 싶어요. 어떤 테이블을 보면 될까요?"
개발자분들도 모든 도메인과 테이블을 파악하고 있지는 않기 때문에, 결국 문의는 데이터팀으로 몰리게 됩니다. 데이터팀 입장에서는 메인 업무 외의 SQL 요청이 꾸준히 쌓이는 구조였죠.
"그럼 그냥 LLM한테 물어보면 되지 않을까?"라는 생각이 드실 수도 있습니다. 실제로 ChatGPT나 Claude 같은 LLM은 SQL을 꽤 잘 만들어줍니다. 하지만 회사의 도메인 지식과 데이터 구조를 알지 못한다는 분명한 한계가 있죠.
즉, 'SQL 작성 능력'은 충분한데 '우리 회사 데이터에 대한 이해'는 전혀 없는 셈입니다.
그래서 이런 생각을 해봤습니다.
LLM이 부족한 "도메인 지식"을 채워 넣고,
구성원들이 익숙한 환경(Slack) 안에서 바로 사용할 수 있는
사내 전용 SQL Agent를 만들어보면 어떨까?
그렇게 DableTalk이 탄생했습니다.
2. SQL Agent, DableTalk
DableTalk은 SQL을 몰라도 자연어로 데이터를 조회할 수 있게 해주는 사내 Text-to-SQL Agent입니다.
현재 알파 버전은 Slack 봇 형태로 제공되며, 한국어 질의와 특정 도메인을 중심으로 운영 중입니다.
DableTalk이 하는 일
•
자연어로 질문을 받고, 회사 도메인 지식을 Context로 사용해 SQL 생성
•
질문 및 결과를 검증하여 오류 최소화
•
Redash(사내 BI Tool)에 쿼리를 자동 생성하고 결과 링크 제공
•
Slack 기반으로, 기존 업무 흐름 안에서 자연스럽게 사용 가능
아래와 같이 채널에서 @mention 하거나 DM으로 자연어 질문을 하면, 해당 스레드에 SQL과 Redash 링크가 함께 달립니다.
3. Step 1: 아키텍처와 질문 처리 플로우
DableTalk은 크게 Core Application, User Interface, Data Layer, Infrastructure 네 레이어로 구성됩니다.
3.1 전체 Architecture
flowchart LR
subgraph User["User Interfaces"]
Slack["Slack Bot<br/>(Slack Bolt, Python)"]
Streamlit["Streamlit Web UI"]
end
subgraph Core["Core Application<br/>VannaDtalkApp"]
Vanna["Vanna AI Framework"]
Gemini["Google Gemini API"]
ChromaClient["ChromaDB Client"]
end
subgraph DataLayer["Data Layer"]
ChromaDB["ChromaDB<br/>Vector Storage"]
Trino["Trino<br/>Analytics DB"]
Postgres["PostgreSQL<br/>Application Data"]
end
subgraph Infra["Infrastructure"]
K8s["Kubernetes"]
ECR["ECR (Containers)"]
Secrets["AWS Secrets Manager"]
end
Slack --> Core
Streamlit --> Core
Core --> Vanna
Core --> Gemini
Core --> ChromaClient
ChromaClient --> ChromaDB
Core --> Trino
Core --> Postgres
Core --> K8s
K8s --> ECR
Core --> Secrets
Mermaid
복사
•
User Interfaces
◦
Slack Bot: 실 사용자가 사용하는 인터페이스
◦
Streamlit Web UI: 관리자용 웹 UI
•
Core Application (VannaDtalkApp)
◦
Vanna AI Framework를 기반으로 구현
◦
Gemini API / ChromaDB / Trino를 조합해 질문 → SQL → 검증까지 처리
•
Data Layer
◦
ChromaDB: SQL/DDL/문서 등의 embedding 저장소
◦
Trino: SQL 검증(EXPLAIN) 및 메타 쿼리 실행
◦
PostgreSQL: 질문/응답 히스토리 및 내부 메타데이터 저장
•
Infrastructure
◦
Kubernetes에 서비스 배포
◦
Secrets Manager로 각종 credential 관리
3.2 질문 처리 플로우
질문이 들어오면 내부에서 다음과 같은 흐름으로 처리됩니다.
graph TD
A[사용자] --> B{Slack 스레드에서 @dtalk mention으로 질문}
B --> C[dtalk 서버]
C --> D[질문 검증 후 SQL 생성 Prompt 구성]
D --> E[Gemini-2.5 Pro로 SQL 생성 요청]
E --> F[생성된 SQL을 Trino로 EXPLAIN으로 검증]
F --> G{SQL 생성 성공?}
G --> H[성공 시 Redash Query 자동 생성]
H --> I[Slack 스레드로 SQL + Query 링크 응답]
I --> J[History 저장]
G --> L[실패 시 오류 정보 + Prompt 함께 응답]
L --> M[History 저장]
Mermaid
복사
4. Step 2: 질문 → SQL까지의 핵심 기능
DableTalk이 텍스트를 SQL로 바꾸는 과정은 단순히 "LLM 한 번 호출"이 아니라,
질문 유효성 판단 → 컨텍스트 검색 → SQL 생성 → 문법 검증의 여러 단계를 거칩니다.
4.1 질문 유효성 판단
먼저, 들어온 문장이 정말 데이터 조회를 위한 질문인지 확인합니다.
단순 잡담이거나 의미 없는 문자열이라면, SQL을 생성하는 대신 안내 메시지를 반환합니다.
이 과정을 통해 처리해야 할 질문과 그렇지 않은 질문을 초기에 구분하여 불필요한 리소스 사용을 줄였습니다.
validation_prompt = f"""
You are a question-validating AI. Your primary function is to analyze a user's question and classify it according to the rules below. You must respond **only** with a JSON object.
...
1. Valid Question
- Condition: The question is a well-formed request to generate an SQL query and falls within the service's scope.
- JSON Output:
{
"question_type": "valid"
}
...
# 빈 문자열, 무의미한 문자열 등 포맷에 문제가 있는 경우
3. Invalid Question: Wrong Format
- Condition: The question is empty, contains only whitespace, or is a meaningless jumble of characters
- JSON Output:
{
"question_type": "invalid-wrong-format",
"respond_message": "{A short Korean message explaining why the format is invalid}"
}
# 점심 추천해줘, 회사 주소를 알려줘 등 SQL 생성과 관련 없는 질문 제외
4. Invalid Question: Unsupported Content
- Condition: The question is grammatically correct and understandable, but its content is outside the service's supported scope.
- JSON Output:
{
"question_type": "invalid-not-support",
"respond_message": "{A short Korean message explaining why the content is not supported}"
}
"""
Markdown
복사
4.2 자연어 → SQL 생성 (RAG + Prompting)
질문이 유효하다고 판단되면, 관련 컨텍스트를 찾는 과정이 시작됩니다.
여기에는 ChromaDB 기반 Vector Search를 사용했습니다.
# Vector DB에서 유사 SQL / 문서 검색
similar_sql = ChromaDB_VectorStore._extract_documents(
self.sql_collection.query(
query_texts=[question],
n_results=self.n_results_sql,
where_document=where_document
)
)
Python
복사
검색된 SQL/DDL/문서를 바탕으로 아래와 같은 형태의 Prompt를 구성합니다.
prompt = f"""
You are an expert Trino SQL analyst at Dable, ...
Please help to generate a SQL query to answer the question.
## Context and Table Guidelines:
- If you don't have enough context or information about the required tables/columns to answer the question, DO NOT create non-existent tables or columns.
...
Question: "{user_question}"
SQL : "{sql_list}",
DDL : "{ddl_list}",
Documentation : "{doc_list}"
"""
sql = llm.generate(prompt)
Python
복사
이 과정에서 핵심은 다음 두 가지였습니다.
•
도메인 Context를 충분히 제공하며
•
없는 테이블/컬럼을 만들어내지 않도록 강한 제약을 거는 것
4.3 SQL 문법 검증 (Trino EXPLAIN)
충분한 컨텍스트를 제공해도 LLM이 생성한 SQL에 오류가 있을 수 있습니다.
따라서 답변하기 전에 먼저 EXPLAIN을 통해 문법적/구조적 문제가 없는지 확인합니다.
try:
trino.execute(f"EXPLAIN {candidate_sql}")
is_valid = True
except Exception as e:
is_valid = False
error_message = str(e)
Python
복사
•
유효한 경우: Redash에 쿼리를 생성하고 링크를 반환
•
유효하지 않은 경우: 실패 원인 및 오류를 반환하여 사용자가 재시도하거나 다른 LLM에 다시 질의할 수 있게 유도
4.4 Redash 자동 생성 및 Slack 답변
검증까지 통과한 SQL은 질문자의 권한으로 Redash에 자동 생성됩니다.
Slack 스레드에는 다음과 같은 정보가 함께 답변됩니다.
•
생성된 SQL
•
생성된 Redash Query 링크
Redash Query에는 질문 시간 및 내용 등을 기록해 사용자의 편의성을 높였습니다.
5. Step 3: 운영과 개선 – 데이터 구조와 실행 환경 맥락을 LLM에 전달하기
DableTalk을 만들면서 가장 어려웠던 부분은 회사 내부 데이터의 구조와 용어를 LLM이 전혀 모른다는 점이었습니다. 필요한 맥락을 추가해도 LLM이 이를 제대로 활용하지 못하는 경우가 있었습니다.
이를 해결하기 위해 도메인 지식 보강을 두 방향으로 진행했습니다.
1) 회사 데이터 구조 관련 지식 보강
Dable 사내에서만 통용되는 용어와 형식을 정리하여 전달했습니다.
•
여러 format의 날짜 정보가 저장되는 string column
◦
timestamp가 아닌 string type이므로, 테이블별로 달라지는 데이터 format 기록
◦
예시: hourly 테이블은 '2025-12-25-00', daily 테이블은 '2025-12-25' 등 형태가 다름
•
여러 테이블에서 사용되지만 테이블별로 의미가 다른 필드들
◦
예시: tag_id 필드
▪
table A에서는 광고가 나가는 지면을 의미
▪
table B에서는 광고 분류를 의미
◦
이런 경우 LLM이 혼동하지 않도록 테이블별 필드 의미를 상세하게 작성했습니다.
초기에는 문법적으로는 맞아도 회사 맥락상 잘못된 SQL을 생성하는 경우가 많았지만, 보강 후에는 같은 문제가 발생하지 않았습니다.
2) Trino SQL 환경 특화 지식 보강
LLM이 생성하는 SQL은 일반적인 SQL dialect(MySQL, PostgreSQL 등) 기준인 경우가 많아 Trino에서 지원하지 않는 문법이나 함수가 반복적으로 등장했습니다.
운영 중 자주 발생한 사례:
•
날짜/시간 함수
◦
Trino에서 제공하지 않는 날짜 함수가 반복적으로 사용됨
◦
timestamp
date 변환 시 함수 시그니처가 맞지 않아 에러 발생
이 문제를 줄이기 위해 다음 방식으로 정확도를 개선했습니다.
•
EXPLAIN 검증 과정에서 발생하는 오류를 기록하고 자주 등장하는 SQL 패턴을 수집하여 지원하지 않는 함수, 대체 가능한 함수, 권장 패턴을 문서화
•
RAG 검색 시 가져오는 문서 개수(top-k)를 늘려, Trino 문법 관련 가이드가 항상 컨텍스트에 포함되도록 설정
6. 현재: 조직 안에서 일어나고 있는 변화들
DableTalk 도입 후 가장 크게 체감한 변화는 비개발자 분들의 적극적인 활용입니다.
비개발자 분들 대상으로 사용 경험에 대한 인터뷰를 진행한 결과
•
"어느 테이블에 원하는 데이터가 있는지 찾아주니 시간이 많이 절약됩니다."
•
"데이터를 빨리 확인하고 다음 스텝으로 넘어갈 수 있어서 효율이 높아졌습니다."
등 긍정적인 피드백을 주셨고, 실제로 비개발자 분들이 DableTalk을 활발히 사용하고 계신 것을 확인했습니다.
7. 운영 및 모니터링: History와 피드백 루프
DableTalk이 처리하는 모든 질문/응답은 History 테이블에 저장됩니다.
•
질문 내용
•
생성된 SQL
•
Prompt
•
성공/실패 여부
•
실패 시 에러 메시지
이 정보들은 다음과 같은 용도로 활용됩니다.
•
자주 실패하는 패턴 분석 → Prompt/컨텍스트 구조 개선
•
자주 성공하는 패턴 분석 → 좋은 사례를 Training data로 재사용
•
도메인 별 사용량/요구 파악 → 도메인 확장 우선순위 선정
향후에는 이 피드백을 기반으로 고품질 SQL을 LLM 학습 데이터(RLHF 스타일)로 활용하는 방향도 고민하고 있습니다.
8. 남은 과제와 다음 스텝
DableTalk은 아직 완성된 서비스라기보다, 조직과 함께 성장해 나가는 도구입니다.
현재까지의 운영에서 얻은 인사이트를 바탕으로, 다음과 같은 계획들을 가지고 있습니다.
•
도메인 확장
◦
더 많은 테이블/도메인을 지원하도록 Training data와 context 강화
•
Training data 자동 수집 및 동기화
◦
신규 테이블 및 SQL을 자동으로 학습 데이터에 반영하는 파이프라인 구성
•
사용자 피드백 기반 RLHF
◦
피드백점수가 높은 쿼리를 검증 후 학습 데이터에 반영
•
SQL 실행 엔진 확장
◦
Trino뿐 아니라 Spark SQL, RDB 등 실행 가능한 엔진 확장
•
웹 기반 멀티턴 + 메모리 포함 SQL Agent
◦
단일 질문-응답을 넘어, 대화형으로 맥락을 이어가며 분석을 도와주는 Agent로 발전
9. 마무리
DableTalk은 "조금 덜 불편하게, 조금 더 빨리 데이터를 볼 수 있게 해주는 도구"에서 출발했습니다.
하지만 이 도구 하나가 데이터 접근성과 업무 흐름에 미치는 영향을 보면서, 발전시킬수록 더 큰 가치를 만들어낼 수 있겠다는 확신이 생겼습니다.
이 글이 비슷한 고민을 하고 계신 분들께 작은 참고가 되었으면 합니다.
읽어주셔서 감사합니다.
참고자료
작성자
관련된 글 더 보기

.jpg&blockId=2ce5bbc0-e5c2-8089-9dcd-c449b51eba46&width=3600)






.jpg&blockId=2ce5bbc0-e5c2-8089-9dcd-c449b51eba46&width=512)
.jpg&blockId=2c75bbc0-e5c2-8040-a465-cb50b3ff6052&width=512)







