|
| 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 | + |
0 commit comments