1- import os
21import json
32
43from typing_extensions import TypedDict , Annotated
5- from langgraph .graph import END , StateGraph
64from langgraph .graph .message import add_messages
75
86
97from llm_utils .chains import (
108 query_maker_chain ,
119 profile_extraction_chain ,
1210 query_enrichment_chain ,
11+ question_gate_chain ,
12+ document_suitability_chain ,
1313)
1414
15- from llm_utils .tools import get_info_from_db
1615from llm_utils .retrieval import search_tables
17- from llm_utils .graph_utils .profile_utils import profile_to_text
1816
1917# 노드 식별자 정의
18+ QUESTION_GATE = "question_gate"
19+ EVALUATE_DOCUMENT_SUITABILITY = "evaluate_document_suitability"
2020GET_TABLE_INFO = "get_table_info"
2121TOOL = "tool"
2222TABLE_FILTER = "table_filter"
@@ -30,12 +30,39 @@ class QueryMakerState(TypedDict):
3030 messages : Annotated [list , add_messages ]
3131 user_database_env : str
3232 searched_tables : dict [str , dict [str , str ]]
33+ document_suitability : dict
3334 best_practice_query : str
3435 question_profile : dict
3536 generated_query : str
3637 retriever_name : str
3738 top_n : int
3839 device : str
40+ question_gate_result : dict
41+
42+
43+ # 노드 함수: QUESTION_GATE 노드
44+ def question_gate_node (state : QueryMakerState ):
45+ """
46+ 사용자의 질문이 SQL로 답변 가능한지 판별하고, 구조화된 결과를 반환하는 게이트 노드입니다.
47+
48+ - question_gate_chain 으로 적합성을 판정하여
49+ `question_gate_result`를 설정합니다.
50+
51+ Args:
52+ state (QueryMakerState): 그래프 상태
53+
54+ Returns:
55+ QueryMakerState: 게이트 판정 결과가 반영된 상태
56+ """
57+
58+ question_text = state ["messages" ][0 ].content
59+ suitability = question_gate_chain .invoke ({"question" : question_text })
60+ state ["question_gate_result" ] = {
61+ "reason" : getattr (suitability , "reason" , "" ),
62+ "missing_entities" : getattr (suitability , "missing_entities" , []),
63+ "requires_data_science" : getattr (suitability , "requires_data_science" , False ),
64+ }
65+ return state
3966
4067
4168# 노드 함수: PROFILE_EXTRACTION 노드
@@ -132,6 +159,70 @@ def get_table_info_node(state: QueryMakerState):
132159 return state
133160
134161
162+ # 노드 함수: DOCUMENT_SUITABILITY 노드
163+ def document_suitability_node (state : QueryMakerState ):
164+ """
165+ GET_TABLE_INFO에서 수집된 테이블 후보들에 대해 문서 적합성 점수를 계산하는 노드입니다.
166+
167+ 질문(`messages[0].content`)과 `searched_tables`(테이블→칼럼 설명 맵)를 입력으로
168+ 프롬프트 체인(`document_suitability_chain`)을 호출하고, 결과 딕셔너리를
169+ `document_suitability` 상태 키에 저장합니다.
170+
171+ Returns:
172+ QueryMakerState: 문서 적합성 평가 결과가 포함된 상태
173+ """
174+
175+ # 관련 테이블이 없으면 즉시 반환
176+ if not state .get ("searched_tables" ):
177+ state ["document_suitability" ] = {}
178+ return state
179+
180+ res = document_suitability_chain .invoke (
181+ {
182+ "question" : state ["messages" ][0 ].content ,
183+ "tables" : state ["searched_tables" ],
184+ }
185+ )
186+
187+ items = (
188+ res .get ("results" , [])
189+ if isinstance (res , dict )
190+ else getattr (res , "results" , None )
191+ or (res .model_dump ().get ("results" , []) if hasattr (res , "model_dump" ) else [])
192+ )
193+
194+ normalized = {}
195+ for x in items :
196+ d = (
197+ x .model_dump ()
198+ if hasattr (x , "model_dump" )
199+ else (
200+ x
201+ if isinstance (x , dict )
202+ else {
203+ "table_name" : getattr (x , "table_name" , "" ),
204+ "score" : getattr (x , "score" , 0 ),
205+ "reason" : getattr (x , "reason" , "" ),
206+ "matched_columns" : getattr (x , "matched_columns" , []),
207+ "missing_entities" : getattr (x , "missing_entities" , []),
208+ }
209+ )
210+ )
211+ t = d .get ("table_name" )
212+ if not t :
213+ continue
214+ normalized [t ] = {
215+ "score" : float (d .get ("score" , 0 )),
216+ "reason" : d .get ("reason" , "" ),
217+ "matched_columns" : d .get ("matched_columns" , []),
218+ "missing_entities" : d .get ("missing_entities" , []),
219+ }
220+
221+ state ["document_suitability" ] = normalized
222+
223+ return state
224+
225+
135226# 노드 함수: QUERY_MAKER 노드
136227def query_maker_node (state : QueryMakerState ):
137228 # 사용자 원 질문 + (있다면) 컨텍스트 보강 결과를 하나의 문자열로 결합
0 commit comments