Skip to content

Commit 0075289

Browse files
committed
질문 적합성 판별 기능 추가 및 관련 모듈 업데이트.
- 새로운 질문 게이트 체인과 출력 모델을 구현 - UI에서 결과를 표시하도록 수정함. - 질문 게이트 결과를 처리하는 노드 및 그래프 구성도 포함됨.
1 parent e66387d commit 0075289

File tree

8 files changed

+189
-10
lines changed

8 files changed

+189
-10
lines changed

interface/lang2sql.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"show_sql": "Show SQL",
3131
"show_question_reinterpreted_by_ai": "Show User Question Reinterpreted by AI",
3232
"show_referenced_tables": "Show List of Referenced Tables",
33+
"show_question_gate_result": "Show Question Gate Result",
3334
"show_table": "Show Table",
3435
"show_chart": "Show Chart",
3536
}
@@ -103,8 +104,23 @@ def should_show(_key: str) -> bool:
103104
show_sql_section = has_query and should_show("show_sql")
104105
show_result_desc = has_query and should_show("show_result_description")
105106
show_reinterpreted = has_query and should_show("show_question_reinterpreted_by_ai")
107+
show_gate_result = should_show("show_question_gate_result")
106108
show_table_section = has_query and should_show("show_table")
107109
show_chart_section = has_query and should_show("show_chart")
110+
if show_gate_result and ("question_gate_result" in res):
111+
st.markdown("---")
112+
st.markdown("**Question Gate 결과:**")
113+
details = res.get("question_gate_result")
114+
if details:
115+
passed = details.get("is_sql_like")
116+
if passed is not None:
117+
st.write(f"적합성 통과 여부: `{passed}`")
118+
try:
119+
import json as _json
120+
st.code(_json.dumps(details, ensure_ascii=False, indent=2), language="json")
121+
except Exception:
122+
st.write(details)
123+
108124

109125
if should_show("show_token_usage"):
110126
st.markdown("---")

llm_utils/chains.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
"""
2+
LLM 체인 생성 모듈.
3+
4+
이 모듈은 Lang2SQL에서 사용하는 다양한 LangChain 기반 체인을 정의합니다.
5+
- Query Maker
6+
- Query Enrichment
7+
- Profile Extraction
8+
- Question Gate (SQL 적합성 분류)
9+
"""
110
import os
211
from langchain_core.prompts import (
312
ChatPromptTemplate,
4-
MessagesPlaceholder,
513
SystemMessagePromptTemplate,
614
)
715
from pydantic import BaseModel, Field
16+
from llm_utils.output_parser.question_suitability import QuestionSuitability
817

918
from llm_utils.llm import get_llm
1019

@@ -15,6 +24,11 @@
1524

1625

1726
class QuestionProfile(BaseModel):
27+
"""
28+
자연어 질문의 특징을 구조화해 표현하는 프로파일 모델.
29+
30+
이 프로파일은 이후 컨텍스트 보강 및 SQL 생성 시 힌트로 사용됩니다.
31+
"""
1832
is_timeseries: bool = Field(description="시계열 분석 필요 여부")
1933
is_aggregation: bool = Field(description="집계 함수 필요 여부")
2034
has_filter: bool = Field(description="조건 필터 필요 여부")
@@ -26,6 +40,15 @@ class QuestionProfile(BaseModel):
2640

2741
# QueryMakerChain
2842
def create_query_maker_chain(llm):
43+
"""
44+
SQL 쿼리 생성을 위한 체인을 생성합니다.
45+
46+
Args:
47+
llm: LangChain 호환 LLM 인스턴스
48+
49+
Returns:
50+
Runnable: 입력 프롬프트를 받아 SQL을 생성하는 체인
51+
"""
2952
prompt = get_prompt_template("query_maker_prompt")
3053
query_maker_prompt = ChatPromptTemplate.from_messages(
3154
[
@@ -36,6 +59,15 @@ def create_query_maker_chain(llm):
3659

3760

3861
def create_query_enrichment_chain(llm):
62+
"""
63+
사용자 질문을 메타데이터로 보강하기 위한 체인을 생성합니다.
64+
65+
Args:
66+
llm: LangChain 호환 LLM 인스턴스
67+
68+
Returns:
69+
Runnable: 보강된 질문 텍스트를 반환하는 체인
70+
"""
3971
prompt = get_prompt_template("query_enrichment_prompt")
4072

4173
enrichment_prompt = ChatPromptTemplate.from_messages(
@@ -49,6 +81,15 @@ def create_query_enrichment_chain(llm):
4981

5082

5183
def create_profile_extraction_chain(llm):
84+
"""
85+
질문으로부터 `QuestionProfile`을 추출하는 체인을 생성합니다.
86+
87+
Args:
88+
llm: LangChain 호환 LLM 인스턴스
89+
90+
Returns:
91+
Runnable: `QuestionProfile` 구조화 출력을 반환하는 체인
92+
"""
5293
prompt = get_prompt_template("profile_extraction_prompt")
5394

5495
profile_prompt = ChatPromptTemplate.from_messages(
@@ -61,9 +102,28 @@ def create_profile_extraction_chain(llm):
61102
return chain
62103

63104

105+
def create_question_gate_chain(llm):
106+
"""
107+
질문 적합성(Question Gate) 체인을 생성합니다.
108+
109+
ChatPromptTemplate(SystemMessage) + LLM 구조화 출력으로
110+
`QuestionSuitability`를 반환합니다.
111+
112+
Args:
113+
llm: LangChain 호환 LLM 인스턴스
114+
115+
Returns:
116+
Runnable: invoke({"question": str}) -> QuestionSuitability
117+
"""
118+
119+
prompt = get_prompt_template("question_gate_prompt")
120+
gate_prompt = ChatPromptTemplate.from_messages(
121+
[SystemMessagePromptTemplate.from_template(prompt)]
122+
)
123+
return gate_prompt | llm.with_structured_output(QuestionSuitability)
124+
125+
64126
query_maker_chain = create_query_maker_chain(llm)
65127
profile_extraction_chain = create_profile_extraction_chain(llm)
66128
query_enrichment_chain = create_query_enrichment_chain(llm)
67-
68-
if __name__ == "__main__":
69-
pass
129+
question_gate_chain = create_question_gate_chain(llm)

llm_utils/graph_utils/base.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import os
21
import json
32

43
from typing_extensions import TypedDict, Annotated
5-
from langgraph.graph import END, StateGraph
64
from langgraph.graph.message import add_messages
75

86

97
from llm_utils.chains import (
108
query_maker_chain,
119
profile_extraction_chain,
1210
query_enrichment_chain,
11+
question_gate_chain,
1312
)
1413

15-
from llm_utils.tools import get_info_from_db
1614
from llm_utils.retrieval import search_tables
17-
from llm_utils.graph_utils.profile_utils import profile_to_text
1815

1916
# 노드 식별자 정의
17+
QUESTION_GATE = "question_gate"
2018
GET_TABLE_INFO = "get_table_info"
2119
TOOL = "tool"
2220
TABLE_FILTER = "table_filter"
@@ -36,6 +34,31 @@ class QueryMakerState(TypedDict):
3634
retriever_name: str
3735
top_n: int
3836
device: str
37+
question_gate_result: dict
38+
39+
# 노드 함수: QUESTION_GATE 노드
40+
def question_gate_node(state: QueryMakerState):
41+
"""
42+
사용자의 질문이 SQL로 답변 가능한지 판별하고, 구조화된 결과를 반환하는 게이트 노드입니다.
43+
44+
- question_gate_chain 으로 적합성을 판정하여
45+
`question_gate_result`를 설정합니다.
46+
47+
Args:
48+
state (QueryMakerState): 그래프 상태
49+
50+
Returns:
51+
QueryMakerState: 게이트 판정 결과가 반영된 상태
52+
"""
53+
54+
question_text = state["messages"][0].content
55+
suitability = question_gate_chain.invoke({"question": question_text})
56+
state["question_gate_result"] = {
57+
"reason": getattr(suitability, "reason", ""),
58+
"missing_entities": getattr(suitability, "missing_entities", []),
59+
"requires_data_science": getattr(suitability, "requires_data_science", False),
60+
}
61+
return state
3962

4063

4164
# 노드 함수: PROFILE_EXTRACTION 노드

llm_utils/graph_utils/basic_graph.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from langgraph.graph import StateGraph, END
44
from llm_utils.graph_utils.base import (
55
QueryMakerState,
6+
QUESTION_GATE,
67
GET_TABLE_INFO,
78
QUERY_MAKER,
9+
question_gate_node,
810
get_table_info_node,
911
query_maker_node,
1012
)
@@ -16,12 +18,25 @@
1618

1719
# StateGraph 생성 및 구성
1820
builder = StateGraph(QueryMakerState)
19-
builder.set_entry_point(GET_TABLE_INFO)
21+
builder.set_entry_point(QUESTION_GATE)
2022

2123
# 노드 추가
24+
builder.add_node(QUESTION_GATE, question_gate_node)
2225
builder.add_node(GET_TABLE_INFO, get_table_info_node)
2326
builder.add_node(QUERY_MAKER, query_maker_node)
2427

28+
def _route_after_gate(state: QueryMakerState):
29+
return GET_TABLE_INFO
30+
31+
builder.add_conditional_edges(
32+
QUESTION_GATE,
33+
_route_after_gate,
34+
{
35+
GET_TABLE_INFO: GET_TABLE_INFO,
36+
END: END,
37+
},
38+
)
39+
2540
# 기본 엣지 설정
2641
builder.add_edge(GET_TABLE_INFO, QUERY_MAKER)
2742

llm_utils/graph_utils/enriched_graph.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
from langgraph.graph import StateGraph, END
44
from llm_utils.graph_utils.base import (
55
QueryMakerState,
6+
QUESTION_GATE,
67
GET_TABLE_INFO,
78
PROFILE_EXTRACTION,
89
CONTEXT_ENRICHMENT,
910
QUERY_MAKER,
11+
question_gate_node,
1012
get_table_info_node,
1113
profile_extraction_node,
1214
context_enrichment_node,
@@ -20,14 +22,27 @@
2022

2123
# StateGraph 생성 및 구성
2224
builder = StateGraph(QueryMakerState)
23-
builder.set_entry_point(GET_TABLE_INFO)
25+
builder.set_entry_point(QUESTION_GATE)
2426

2527
# 노드 추가
28+
builder.add_node(QUESTION_GATE, question_gate_node)
2629
builder.add_node(GET_TABLE_INFO, get_table_info_node)
2730
builder.add_node(PROFILE_EXTRACTION, profile_extraction_node)
2831
builder.add_node(CONTEXT_ENRICHMENT, context_enrichment_node)
2932
builder.add_node(QUERY_MAKER, query_maker_node)
3033

34+
def _route_after_gate(state: QueryMakerState):
35+
return GET_TABLE_INFO
36+
37+
builder.add_conditional_edges(
38+
QUESTION_GATE,
39+
_route_after_gate,
40+
{
41+
GET_TABLE_INFO: GET_TABLE_INFO,
42+
END: END,
43+
},
44+
)
45+
3146
# 기본 엣지 설정
3247
builder.add_edge(GET_TABLE_INFO, PROFILE_EXTRACTION)
3348
builder.add_edge(PROFILE_EXTRACTION, CONTEXT_ENRICHMENT)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""
2+
출력 파서 모듈 패키지 초기화.
3+
4+
이 패키지는 LLM의 구조화 출력 모델과 파서들을 포함합니다.
5+
"""
6+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
QuestionSuitability 출력 모델.
3+
4+
LLM 구조화 출력으로부터 SQL 적합성 판단 결과를 표현하는 Pydantic 모델입니다.
5+
"""
6+
7+
from pydantic import BaseModel, Field
8+
9+
10+
class QuestionSuitability(BaseModel):
11+
"""
12+
SQL 생성 적합성 결과 모델.
13+
14+
LLM 구조화 출력으로 직렬화 가능한 필드를 정의합니다.
15+
"""
16+
17+
reason: str = Field(description="보완/설명 사유 요약")
18+
missing_entities: list[str] = Field(
19+
default_factory=list, description="질문에서 누락된 핵심 엔터티/기간 등"
20+
)
21+
requires_data_science: bool = Field(
22+
default=False, description="SQL을 넘어 ML/통계 분석이 필요한지 여부"
23+
)
24+
25+

prompt/question_gate_prompt.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
당신은 데이터 분석 도우미입니다. 아래 사용자 질문이 SQL로 답변 가능한지 판별하고, 구조화된 결과를 반환하세요.
2+
3+
요건:
4+
- reason: 한 줄 설명(어떤 보완이 필요한지 요약)
5+
- missing_entities: 기간, 대상 엔터티, 측정값 등 누락된 핵심 요소 리스트(없으면 빈 리스트)
6+
- requires_data_science: 통계/ML 분석이 필요한지 여부(Boolean)
7+
8+
언어/출력 형식:
9+
- 모든 텍스트 값은 한국어로 작성하세요. (reason는 한국어 문장, missing_entities 항목은 한국어 명사구)
10+
- Boolean 값은 JSON의 true/false로 표기하세요.
11+
12+
주의:
13+
- 데이터 분석 맥락에서 SQL 집계/필터/조인으로 해결 가능한지 판단합니다.
14+
- 정책/운영/가이드/설치/권한/오류 해결 등은 SQL 부적합으로 간주합니다.
15+
16+
입력: {question}
17+
18+
출력은 반드시 지정된 스키마의 JSON으로만 반환하세요.
19+

0 commit comments

Comments
 (0)