Skip to content

Commit 7a7935b

Browse files
committed
그래프 빌더 기능 추가 #134
- Streamlit에서 LangGraph 워크플로우를 구성하고 세션에 적용하는 페이지 구현. - 프리셋 및 커스텀 옵션을 통해 노드 시퀀스를 설정하고, QUERY_MAKER 포함 여부에 따라 그래프를 동적으로 생성하도록 수정 - 관련 UI 요소 및 세션 상태 관리 기능 추가.
1 parent 640b2f0 commit 7a7935b

File tree

3 files changed

+202
-6
lines changed

3 files changed

+202
-6
lines changed

interface/graph_builder.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
LangGraph 워크플로우를 Streamlit에서 구성하고 세션에 적용하는 페이지.
3+
4+
기능 개요:
5+
- 프리셋(기본/확장) 또는 커스텀 토글로 노드 시퀀스를 구성
6+
- QUERY_MAKER 포함 여부를 토글하여 마지막 노드를 제어
7+
- 선택이 바뀌면 즉시 컴파일된 그래프를 세션 상태에 반영
8+
- 현재 적용된 그래프 설정을 확인 가능
9+
"""
10+
11+
from typing import List
12+
13+
import streamlit as st
14+
from langgraph.graph import StateGraph, END
15+
16+
from llm_utils.graph_utils.base import (
17+
QueryMakerState,
18+
GET_TABLE_INFO,
19+
PROFILE_EXTRACTION,
20+
CONTEXT_ENRICHMENT,
21+
QUERY_MAKER,
22+
get_table_info_node,
23+
profile_extraction_node,
24+
context_enrichment_node,
25+
query_maker_node,
26+
)
27+
28+
29+
def build_selected_sequence(preset: str, use_profile: bool, use_context: bool) -> List[str]:
30+
"""
31+
프리셋과 커스텀 토글에 따라 실행할 노드 시퀀스를 생성합니다.
32+
33+
Args:
34+
preset (str): "기본" | "확장" | "커스텀" 중 하나
35+
use_profile (bool): 커스텀에서 PROFILE_EXTRACTION 포함 여부
36+
use_context (bool): 커스텀에서 CONTEXT_ENRICHMENT 포함 여부
37+
38+
Returns:
39+
List[str]: 노드 식별자들의 실행 순서
40+
"""
41+
sequence: List[str] = [GET_TABLE_INFO]
42+
43+
if preset == "기본":
44+
sequence += [QUERY_MAKER]
45+
elif preset == "확장":
46+
sequence += [PROFILE_EXTRACTION, CONTEXT_ENRICHMENT, QUERY_MAKER]
47+
else:
48+
if use_profile:
49+
sequence.append(PROFILE_EXTRACTION)
50+
if use_context:
51+
sequence.append(CONTEXT_ENRICHMENT)
52+
sequence.append(QUERY_MAKER)
53+
54+
return sequence
55+
56+
57+
def build_state_graph(sequence: List[str]) -> StateGraph:
58+
"""
59+
주어진 시퀀스대로 노드를 추가하고, 인접 노드 간 엣지를 연결한 그래프 빌더를 반환합니다.
60+
61+
마지막 노드는 항상 END로 연결합니다.
62+
63+
Args:
64+
sequence (List[str]): 실행 순서에 따른 노드 식별자 목록
65+
66+
Returns:
67+
StateGraph: 컴파일 전 그래프 빌더 객체
68+
"""
69+
builder = StateGraph(QueryMakerState)
70+
builder.set_entry_point(GET_TABLE_INFO)
71+
72+
# 노드 등록
73+
for node_id in sequence:
74+
if node_id == GET_TABLE_INFO:
75+
builder.add_node(GET_TABLE_INFO, get_table_info_node)
76+
elif node_id == PROFILE_EXTRACTION:
77+
builder.add_node(PROFILE_EXTRACTION, profile_extraction_node)
78+
elif node_id == CONTEXT_ENRICHMENT:
79+
builder.add_node(CONTEXT_ENRICHMENT, context_enrichment_node)
80+
elif node_id == QUERY_MAKER:
81+
builder.add_node(QUERY_MAKER, query_maker_node)
82+
83+
# 엣지 연결
84+
for i in range(len(sequence) - 1):
85+
builder.add_edge(sequence[i], sequence[i + 1])
86+
87+
# 종료 연결: 마지막 노드가 무엇이든 END로 연결
88+
if len(sequence) > 0:
89+
builder.add_edge(sequence[-1], END)
90+
91+
return builder
92+
93+
94+
def render_sequence(sequence: List[str]) -> str:
95+
"""
96+
노드 시퀀스를 사람이 읽기 쉬운 문자열로 변환합니다.
97+
98+
Args:
99+
sequence (List[str]): 실행 순서에 따른 노드 식별자 목록
100+
101+
Returns:
102+
str: 예) "GET_TABLE_INFO → PROFILE_EXTRACTION → ..."
103+
"""
104+
label_map = {
105+
GET_TABLE_INFO: "GET_TABLE_INFO",
106+
PROFILE_EXTRACTION: "PROFILE_EXTRACTION",
107+
CONTEXT_ENRICHMENT: "CONTEXT_ENRICHMENT",
108+
QUERY_MAKER: "QUERY_MAKER",
109+
}
110+
return " → ".join(label_map[s] for s in sequence)
111+
112+
113+
114+
115+
116+
st.title("LangGraph 구성 UI")
117+
st.caption("기본/확장/커스텀으로 StateGraph를 구성하고 세션에 적용합니다.")
118+
119+
preset = st.radio("프리셋 선택", ("기본", "확장", "커스텀"), horizontal=True)
120+
121+
use_profile = False
122+
use_context = False
123+
if preset == "커스텀":
124+
st.subheader("커스텀 옵션")
125+
use_profile = st.checkbox("PROFILE_EXTRACTION 포함", value=True)
126+
use_context = st.checkbox("CONTEXT_ENRICHMENT 포함", value=True)
127+
use_query_maker = st.checkbox("QUERY_MAKER 포함", value=True)
128+
else:
129+
# 프리셋에서는 QUERY_MAKER 자동 포함
130+
use_query_maker = True
131+
132+
def build_sequence_with_qm(preset: str, use_profile: bool, use_context: bool, use_qm: bool) -> List[str]:
133+
"""
134+
QUERY_MAKER 포함 여부를 반영하여 시퀀스를 생성합니다.
135+
136+
- use_qm=False면 마지막 노드는 반드시 GET_TABLE_INFO입니다.
137+
- use_qm=True면 프리셋/커스텀 로직에 따라 마지막 노드는 QUERY_MAKER가 됩니다.
138+
139+
Args:
140+
preset (str): "기본" | "확장" | "커스텀" 중 하나
141+
use_profile (bool): PROFILE_EXTRACTION 포함 여부(커스텀 전용)
142+
use_context (bool): CONTEXT_ENRICHMENT 포함 여부(커스텀 전용)
143+
use_qm (bool): QUERY_MAKER 포함 여부
144+
145+
Returns:
146+
List[str]: 노드 식별자들의 실행 순서
147+
"""
148+
# QUERY_MAKER가 비활성화되면 마지막 노드는 반드시 GET_TABLE_INFO
149+
if not use_qm:
150+
return [GET_TABLE_INFO]
151+
# 활성화된 경우 프리셋/커스텀 구성에 따라 마지막 노드는 QUERY_MAKER
152+
base_seq = build_selected_sequence(preset, use_profile, use_context)
153+
return base_seq
154+
155+
sequence = build_sequence_with_qm(preset, use_profile, use_context, use_query_maker)
156+
157+
st.subheader("실행 순서")
158+
st.write(render_sequence(sequence))
159+
160+
st.subheader("그래프 생성")
161+
config = {"preset": preset, "use_profile": use_profile, "use_context": use_context, "use_query_maker": use_query_maker}
162+
163+
# 선택이 바뀌면 자동으로 세션 그래프 갱신
164+
prev_config = st.session_state.get("graph_config")
165+
if ("graph" not in st.session_state) or (prev_config != config):
166+
_builder = build_state_graph(sequence)
167+
st.session_state["graph"] = _builder.compile()
168+
st.session_state["graph_config"] = config
169+
st.info("그래프가 세션에 적용되었습니다.")
170+
171+
# 수동 새로고침 버튼
172+
if st.button("세션 그래프 새로고침"):
173+
_builder = build_state_graph(sequence)
174+
st.session_state["graph"] = _builder.compile()
175+
st.session_state["graph_config"] = config
176+
st.success("세션 그래프가 새로고침되었습니다.")
177+
178+
with st.expander("현재 세션 그래프 설정"):
179+
st.json(st.session_state.get("graph_config", {}))
180+

interface/lang2sql.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ def display_result(
9898
def should_show(_key: str) -> bool:
9999
return st.session_state.get(_key, True)
100100

101+
has_query = bool(res.get("generated_query"))
102+
# 섹션 표시 여부를 QUERY_MAKER 출력 유무에 따라 제어
103+
show_sql_section = has_query and should_show("show_sql")
104+
show_result_desc = has_query and should_show("show_result_description")
105+
show_reinterpreted = has_query and should_show("show_question_reinterpreted_by_ai")
106+
show_table_section = has_query and should_show("show_table")
107+
show_chart_section = has_query and should_show("show_chart")
108+
101109
if should_show("show_token_usage"):
102110
st.markdown("---")
103111
token_summary = TokenUtils.get_token_usage_summary(data=res["messages"])
@@ -110,7 +118,7 @@ def should_show(_key: str) -> bool:
110118
"""
111119
)
112120

113-
if should_show("show_sql"):
121+
if show_sql_section:
114122
st.markdown("---")
115123
generated_query = res.get("generated_query")
116124
if generated_query:
@@ -138,7 +146,7 @@ def should_show(_key: str) -> bool:
138146
st.warning("쿼리 텍스트가 문자열이 아닙니다.")
139147
st.text(str(query_text))
140148

141-
if should_show("show_result_description"):
149+
if show_result_desc:
142150
st.markdown("---")
143151
st.markdown("**결과 설명:**")
144152
result_message = res["messages"][-1].content
@@ -158,7 +166,7 @@ def should_show(_key: str) -> bool:
158166
st.warning("결과 메시지가 문자열이 아닙니다.")
159167
st.text(str(result_message))
160168

161-
if should_show("show_question_reinterpreted_by_ai"):
169+
if show_reinterpreted:
162170
st.markdown("---")
163171
st.markdown("**AI가 재해석한 사용자 질문:**")
164172
try:
@@ -178,7 +186,11 @@ def should_show(_key: str) -> bool:
178186
st.markdown("**참고한 테이블 목록:**")
179187
st.write(res.get("searched_tables", []))
180188

181-
if should_show("show_table"):
189+
# QUERY_MAKER가 비활성화된 경우 안내 메시지 출력
190+
if not has_query:
191+
st.info("QUERY_MAKER 없이 실행되었습니다. 검색된 테이블 정보만 표시합니다.")
192+
193+
if show_table_section:
182194
st.markdown("---")
183195
try:
184196
sql_raw = (
@@ -195,7 +207,7 @@ def should_show(_key: str) -> bool:
195207
except Exception as e:
196208
st.error(f"쿼리 실행 중 오류 발생: {e}")
197209

198-
if should_show("show_chart"):
210+
if show_chart_section:
199211
st.markdown("---")
200212
try:
201213
sql_raw = (

interface/streamlit_app.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
"lang2sql": {
1212
"page": "lang2sql.py",
1313
"title": "Lang2SQL",
14-
}
14+
},
15+
"graph_builder": {
16+
"page": "graph_builder.py",
17+
"title": "Graph Builder",
18+
},
1519
}
1620

1721

0 commit comments

Comments
 (0)