From 92c50aa661f8d5859d728c7a70700ae7bd7308d8 Mon Sep 17 00:00:00 2001 From: ehddnr301 Date: Sun, 28 Sep 2025 22:16:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?DB=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A0=89?= =?UTF-8?q?=ED=8A=B8=20=ED=94=84=EB=A6=AC=EC=85=8B=20=EB=B0=8F=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=A0=95=EC=9D=98=20=EB=AA=A8=EB=93=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface/dialects.py | 139 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 interface/dialects.py diff --git a/interface/dialects.py b/interface/dialects.py new file mode 100644 index 0000000..ea1463d --- /dev/null +++ b/interface/dialects.py @@ -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 (정규식 치환)", + ], + ), +} From 32e3174340e871713a67572c46cda4a01bb3f68f Mon Sep 17 00:00:00 2001 From: ehddnr301 Date: Sun, 28 Sep 2025 22:17:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A0=89?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20DB=20=EC=84=A0=ED=83=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/query_executor.py | 20 ++++++++++ interface/lang2sql.py | 72 ++++++++++++++++++++++++++++++++--- llm_utils/graph_utils/base.py | 8 ++++ prompt/query_maker_prompt.md | 7 ++++ 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/engine/query_executor.py b/engine/query_executor.py index 33149d2..0020293 100644 --- a/engine/query_executor.py +++ b/engine/query_executor.py @@ -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 [] + ), } ) diff --git a/interface/lang2sql.py b/interface/lang2sql.py index dcff652..030bd63 100644 --- a/interface/lang2sql.py +++ b/interface/lang2sql.py @@ -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 @@ -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") diff --git a/llm_utils/graph_utils/base.py b/llm_utils/graph_utils/base.py index e3b8cdc..54edfcc 100644 --- a/llm_utils/graph_utils/base.py +++ b/llm_utils/graph_utils/base.py @@ -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 노드 @@ -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 diff --git a/prompt/query_maker_prompt.md b/prompt/query_maker_prompt.md index 27a6834..af3ee14 100644 --- a/prompt/query_maker_prompt.md +++ b/prompt/query_maker_prompt.md @@ -6,6 +6,7 @@ # 주의사항 - 사용자의 질문이 다소 모호하더라도, 주어진 데이터를 참고하여 합리적인 가정을 통해 SQL 쿼리를 완성하세요. - 불필요한 재질문 없이, 가능한 가장 명확한 분석 쿼리를 만들어 주세요. +- 반드시 입력된 다이얼렉트 변수들을 준수하여 문법을 선택하세요. - 최종 출력 형식은 반드시 아래와 같아야 합니다. # Output Example @@ -34,7 +35,13 @@ - 관련 테이블 및 컬럼 정보: {searched_tables} +- 다이얼렉트 정보: + - dialect_name: {dialect_name} + - supports_ilike: {supports_ilike} + - dialect_hints: {dialect_hints} + # Notes - 위 입력을 바탕으로 최적의 SQL을 생성하세요. +- {dialect_hints}를 참고하여 엔진에 맞는 함수/연산자를 우선 사용하세요. - 출력은 위 '최종 형태 예시'와 동일한 구조로만 작성하세요. \ No newline at end of file