Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions engine/query_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ def execute_query(
"retriever_name": retriever_name,
"top_n": top_n,
"device": device,
# 다이얼렉트 정보 주입 (있다면 세션에서, 없으면 기본값)
"dialect_name": (
session_state.get("selected_dialect_option", {}).get("name")
if session_state is not None
else database_env
),
"supports_ilike": (
bool(
session_state.get("selected_dialect_option", {}).get(
"supports_ilike", False
)
)
if session_state is not None
else False
),
"dialect_hints": (
session_state.get("selected_dialect_option", {}).get("hints", [])
if session_state is not None
else []
),
}
)

Expand Down
139 changes: 139 additions & 0 deletions interface/dialects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""
다이얼렉트 프리셋과 옵션 정의 모듈.

이 모듈은 다음을 제공합니다:

- DialectOption: 각 SQL 엔진 특성 데이터클래스
- name: 엔진 표시 이름 (예: "PostgreSQL", "ClickHouse")
- supports_ilike: 대소문자 무시 비교(ILIKE) 지원 여부
- hints: 자주 쓰이는/효과적인 함수의 간결 목록 + 짧은 메모
- 예: ["DATE_TRUNC (날짜 절단)", "STRING_AGG (문자 집계)"]

- PRESET_DIALECTS: 대표 SQL 엔진들의 기본 프리셋 모음
- PostgreSQL, ClickHouse, Trino, Snowflake, Redshift, BigQuery, MSSQL, Oracle, DuckDB

주 사용처:
- Streamlit UI에서 프리셋 선택 및 커스텀 다이얼렉트 입력의 기준 데이터
- Lang2SQL 파이프라인에서 프롬프트/키워드 힌트 구성

주의:
- hints는 프롬프트 가이드용이며 실행 보장을 의미하지 않습니다.
- 실제 문법/함수 지원은 엔진 버전 및 설정에 따라 달라질 수 있습니다.
"""

from __future__ import annotations

from dataclasses import asdict, dataclass, field
from typing import Dict, List


@dataclass
class DialectOption:
name: str
supports_ilike: bool = False
hints: List[str] = field(default_factory=list)

def to_dict(self) -> Dict:
return asdict(self)

@staticmethod
def from_dict(data: Dict) -> "DialectOption":
return DialectOption(
name=data.get("name", "Custom"),
supports_ilike=bool(data.get("supports_ilike", False)),
hints=list(data.get("hints", data.get("keyword_hints", []))),
)


PRESET_DIALECTS: Dict[str, DialectOption] = {
"PostgreSQL": DialectOption(
name="PostgreSQL",
supports_ilike=True,
hints=[
"COALESCE (널 대체)",
"DATE_TRUNC (날짜 절단)",
"STRING_AGG (문자 집계)",
"GENERATE_SERIES (시퀀스 생성)",
],
),
"ClickHouse": DialectOption(
name="ClickHouse",
supports_ilike=False,
hints=[
"toDate (날짜 변환)",
"dateDiff (날짜 차이)",
"arrayJoin (배열 펼치기)",
"groupArray (배열 집계)",
],
),
"Trino": DialectOption(
name="Trino",
supports_ilike=False,
hints=[
"date_trunc (날짜 절단)",
"try_cast (안전 변환)",
"coalesce (널 대체)",
"regexp_like (정규식 매칭)",
],
),
"Snowflake": DialectOption(
name="Snowflake",
supports_ilike=True,
hints=[
"IFF (조건 분기)",
"TO_DATE (날짜 변환)",
"DATE_TRUNC (날짜 절단)",
"LISTAGG (문자 집계)",
],
),
"Redshift": DialectOption(
name="Redshift",
supports_ilike=True,
hints=[
"COALESCE (널 대체)",
"DATE_TRUNC (날짜 절단)",
"LISTAGG (문자 집계)",
"REGEXP_REPLACE (정규식 치환)",
],
),
"BigQuery": DialectOption(
name="BigQuery",
supports_ilike=False,
hints=[
"SAFE_CAST (안전 변환)",
"DATE_TRUNC (날짜 절단)",
"ARRAY_AGG (배열 집계)",
"REGEXP_CONTAINS (정규식 포함)",
],
),
"MSSQL": DialectOption(
name="MSSQL",
supports_ilike=False,
hints=[
"ISNULL (널 대체)",
"DATEADD (날짜 가감)",
"CONVERT (형 변환)",
"STRING_AGG (문자 집계)",
],
),
"Oracle": DialectOption(
name="Oracle",
supports_ilike=False,
hints=[
"NVL (널 대체)",
"TO_DATE (날짜 변환)",
"TRUNC (날짜 절단)",
"LISTAGG (문자 집계)",
],
),
"DuckDB": DialectOption(
name="DuckDB",
supports_ilike=True,
hints=[
"date_trunc (날짜 절단)",
"string_agg (문자 집계)",
"coalesce (널 대체)",
"regexp_replace (정규식 치환)",
],
),
}
72 changes: 66 additions & 6 deletions interface/lang2sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import re

import streamlit as st
from langchain.chains.sql_database.prompt import SQL_PROMPTS
from langchain_core.messages import AIMessage
from interface.dialects import PRESET_DIALECTS, DialectOption
from copy import deepcopy

from db_utils import get_db_connector
from db_utils.base_connector import BaseConnector
Expand Down Expand Up @@ -344,11 +345,70 @@ def _as_float(value):
"쿼리를 입력하세요:",
value=DEFAULT_QUERY,
)
user_database_env = st.selectbox(
"DB 환경정보를 입력하세요:",
options=SQL_PROMPTS.keys(),
index=0,
)

# DB 프리셋을 세션에 로드(편집 가능)
if "dialects" not in st.session_state:
st.session_state["dialects"] = {k: v.to_dict() for k, v in PRESET_DIALECTS.items()}

st.markdown("### DB 선택 및 관리")
cols = st.columns(2)

# 공통 변수 최소화
dialects = st.session_state["dialects"]
keys = list(dialects.keys())
active = st.session_state.get("active_dialect", keys[0])

with cols[0]:
user_database_env = st.selectbox(
"사용할 DB를 선택하세요:",
options=keys,
index=(keys.index(active) if active in keys else 0),
)
st.session_state["active_dialect"] = user_database_env
st.session_state["selected_dialect_option"] = dialects.get(
user_database_env, dialects[keys[0]]
)

with cols[1]:
st.caption("선택된 DB 설정을 편집하거나 새로 추가할 수 있습니다.")

with st.expander("DB 편집"):
edit_key = st.selectbox(
"편집할 DB를 선택하세요:",
options=keys,
index=(
keys.index(st.session_state["active_dialect"])
if st.session_state.get("active_dialect") in keys
else 0
),
key="dialect_edit_selector",
)
# 편집 대상 선택 시 메인 선택과 동기화
st.session_state["active_dialect"] = edit_key
st.session_state["selected_dialect_option"] = dialects[edit_key]

current = deepcopy(dialects[edit_key])
_supports_ilike = st.checkbox(
"ILIKE 지원", value=bool(current.get("supports_ilike", False))
)
# limit_syntax 제거: hints로 사용자가 커버
_hints_text = st.text_area(
"hints (쉼표로 구분)",
value=", ".join(current.get("hints", [])),
help="예약어/함수/문법 힌트를 쉼표로 구분하여 입력",
)
if st.button("변경사항 저장", key="btn_save_dialect_edit"):
st.session_state["dialects"][edit_key] = DialectOption(
name=edit_key,
supports_ilike=_supports_ilike,
hints=[s.strip() for s in _hints_text.split(",") if s.strip()],
).to_dict()
# 저장 후 선택된 다이얼렉트 옵션도 최신 값으로 동기화
st.session_state["selected_dialect_option"] = st.session_state["dialects"][
edit_key
]
st.success(f"{edit_key} DB가 업데이트되었습니다.")


_device_options = ["cpu", "cuda"]
_default_device = st.session_state.get("default_device", "cpu")
Expand Down
8 changes: 8 additions & 0 deletions llm_utils/graph_utils/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class QueryMakerState(TypedDict):
top_n: int
device: str
question_gate_result: dict
# 다이얼렉트 정보
dialect_name: str
supports_ilike: bool
dialect_hints: list[str]


# 노드 함수: QUESTION_GATE 노드
Expand Down Expand Up @@ -245,6 +249,10 @@ def query_maker_node(state: QueryMakerState):
"user_input": combined_input,
"user_database_env": state["user_database_env"],
"searched_tables": searched_tables_json,
# 다이얼렉트 변수 전달
"dialect_name": state.get("dialect_name", ""),
"supports_ilike": state.get("supports_ilike", False),
"dialect_hints": ", ".join(state.get("dialect_hints", [])),
}
)
state["generated_query"] = res
Expand Down
7 changes: 7 additions & 0 deletions prompt/query_maker_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# 주의사항
- 사용자의 질문이 다소 모호하더라도, 주어진 데이터를 참고하여 합리적인 가정을 통해 SQL 쿼리를 완성하세요.
- 불필요한 재질문 없이, 가능한 가장 명확한 분석 쿼리를 만들어 주세요.
- 반드시 입력된 다이얼렉트 변수들을 준수하여 문법을 선택하세요.
- 최종 출력 형식은 반드시 아래와 같아야 합니다.

# Output Example
Expand Down Expand Up @@ -34,7 +35,13 @@
- 관련 테이블 및 컬럼 정보:
{searched_tables}

- 다이얼렉트 정보:
- dialect_name: {dialect_name}
- supports_ilike: {supports_ilike}
- dialect_hints: {dialect_hints}

# Notes

- 위 입력을 바탕으로 최적의 SQL을 생성하세요.
- {dialect_hints}를 참고하여 엔진에 맞는 함수/연산자를 우선 사용하세요.
- 출력은 위 '최종 형태 예시'와 동일한 구조로만 작성하세요.