11from core .shared .states .states import CustomsAgentState
22from core .qna .main import RAGSystem
3+ from core .shared .utils .llm import get_llm
4+ from langchain_core .messages import HumanMessage
5+ import re
6+
7+ def compare_responses (llm_response : str , rag_response : str , comparison_llm ) -> bool :
8+ """
9+ LLM을 사용하여 두 응답의 유사성을 판단합니다.
10+
11+ Args:
12+ llm_response: LLM 기반 응답
13+ rag_response: RAG 기반 응답
14+ comparison_llm: 비교용 LLM 인스턴스
15+
16+ Returns:
17+ bool: True if responses are similar, False if they differ significantly
18+ """
19+ comparison_prompt = f"""다음은 동일한 질문에 대한 두 개의 답변입니다.
20+ 이 두 답변이 내용적으로 유사한지 판단해주세요.
21+
22+ 답변 1: { llm_response }
23+
24+ 답변 2: { rag_response }
25+
26+ 두 답변의 핵심 내용이 유사하거나 동일하다면 "유사함"이라고 답하고,
27+ 내용이 다르거나 모순되거나 추가 정보가 포함되어 있다면 "다름"이라고 답해주세요.
28+
29+ 답변:"""
30+
31+ try :
32+ comparison_result = comparison_llm .invoke ([HumanMessage (content = comparison_prompt )])
33+ comparison_text = str (comparison_result .content ) if hasattr (comparison_result , 'content' ) else str (comparison_result )
34+
35+ # "유사함"이 포함되어 있으면 True 반환
36+ return "유사함" in comparison_text or "similar" in comparison_text .lower ()
37+ except Exception as e :
38+ # LLM 비교 실패 시 기본 로직 사용
39+ print (f"LLM 비교 실패, 기본 로직 사용: { e } " )
40+
41+ # 간단한 키워드 기반 비교
42+ def extract_keywords (text : str ) -> set :
43+ customs_keywords = [
44+ '관세' , '세금' , '수입' , '수출' , '통관' , '신고' , '세율' , '과세' , '면세' ,
45+ '반입' , '반출' , '검사' , '검역' , '위험물' , '금지' , '제한' , '허가' ,
46+ '서류' , '증명' , '신청' , '처리' , '기간' , '비용' , '요금' , '부과'
47+ ]
48+
49+ keywords = set ()
50+ for keyword in customs_keywords :
51+ if keyword in text :
52+ keywords .add (keyword )
53+ return keywords
54+
55+ llm_keywords = extract_keywords (llm_response .lower ())
56+ rag_keywords = extract_keywords (rag_response .lower ())
57+
58+ if llm_keywords and rag_keywords :
59+ common_keywords = llm_keywords .intersection (rag_keywords )
60+ total_keywords = llm_keywords .union (rag_keywords )
61+ similarity = len (common_keywords ) / len (total_keywords ) if total_keywords else 0
62+ return similarity >= 0.7
63+
64+ return False
365
466def qna_agent (state : CustomsAgentState ) -> CustomsAgentState :
5- """QNA RAG 에이전트 - 실제 RAG 시스템 사용 """
67+ """QNA 에이전트 - RAG 우선, 부족시 LLM 활용 """
668
7- # RAG 시스템 초기화 및 데이터베이스 설정
8- rag_system = RAGSystem ()
9- rag_system .setup_database ()
69+ query = state ["query" ]
1070
11- # RAG 시스템을 사용하여 답변 생성
12- answer = rag_system .search_and_generate (
13- query = state ["query" ],
71+ # 1. RAG 시스템을 사용한 응답 생성 (1차 우선)
72+ rag_system = RAGSystem ()
73+ rag_response = rag_system .search_and_generate (
74+ query = query ,
1475 top_k = 5 ,
1576 show_details = False
1677 )
1778
18- state ["final_response" ] = answer
79+ # 2. RAG 응답의 품질 평가
80+ def evaluate_rag_quality (rag_response : str , query : str ) -> bool :
81+ """LLM을 사용하여 RAG 응답이 충분한 정보를 제공하는지 평가"""
82+ llm = get_llm ()
83+
84+ evaluation_prompt = f"""다음은 사용자의 질문과 RAG 시스템이 제공한 답변입니다.
85+ 이 답변이 사용자의 질문에 대해 충분하고 정확한 정보를 제공하는지 판단해주세요.
86+
87+ 사용자 질문: { query }
88+
89+ RAG 답변: { rag_response }
90+
91+ 다음 중 하나에 해당하면 "부족함"이라고 답하고, 그렇지 않으면 "충분함"이라고 답해주세요:
92+
93+ 1. 답변이 너무 짧거나 구체적이지 않은 경우
94+ 2. "정확한 정보를 제공할 수 없다", "참고할 수 있는 문서가 없다" 등의 문구가 포함된 경우
95+ 3. 질문에 대한 구체적인 답변이 아닌 일반적인 설명만 있는 경우
96+ 4. 문서에 없는 내용을 임의로 생성했다고 명시된 경우
97+
98+ 판단 결과:"""
99+
100+ try :
101+ evaluation_result = llm .invoke ([HumanMessage (content = evaluation_prompt )])
102+ evaluation_text = str (evaluation_result .content ) if hasattr (evaluation_result , 'content' ) else str (evaluation_result )
103+
104+ # "부족함"이 포함되어 있으면 False 반환
105+ return "부족함" not in evaluation_text and "insufficient" not in evaluation_text .lower ()
106+
107+ except Exception as e :
108+ # 기본 로직: 간단한 키워드 체크
109+ insufficient_indicators = [
110+ "정확한 정보를 제공할 수 없다" ,
111+ "참고할 수 있는 문서가 없다" ,
112+ "문서에 없는 내용" ,
113+ "정보가 부족하다" ,
114+ "확실하지 않다"
115+ ]
116+
117+ for indicator in insufficient_indicators :
118+ if indicator in rag_response :
119+ return False
120+
121+ # 길이 체크
122+ if len (rag_response .strip ()) < 50 :
123+ return False
124+
125+ return True
126+
127+ rag_quality_good = evaluate_rag_quality (rag_response , query )
128+
129+ # 3. 최종 응답 선택
130+ if not rag_quality_good :
131+ # RAG 응답이 부족한 경우 LLM 사용 + 불확실성 표시
132+ llm = get_llm ()
133+ llm_prompt = f"""다음은 관세 관련 질문입니다. 사전 학습된 지식만을 사용하여 답변해주세요.
134+
135+ 질문: { query }
136+
137+ 답변:"""
138+
139+ llm_result = llm .invoke ([HumanMessage (content = llm_prompt )])
140+ llm_response = str (llm_result .content ) if hasattr (llm_result , 'content' ) else str (llm_result )
141+
142+ # 불확실성 표시 추가
143+ final_response = f"{ llm_response } \n \n ※ 이 답변은 불확실할 수 있습니다."
144+ response_source = "LLM (RAG 응답 부족)"
145+
146+ else :
147+ # RAG로 충분히 답변할 수 있는 경우 RAG만 사용
148+ final_response = rag_response
149+ response_source = "RAG (외부 지식 기반)"
150+
151+ state ["final_response" ] = final_response
19152 state ["intermediate_results" ]["qna" ] = {
20- "response" : answer ,
21- "query" : state ["query" ]
153+ "rag_response" : rag_response ,
154+ "rag_quality_good" : rag_quality_good ,
155+ "selected_response" : final_response ,
156+ "response_source" : response_source ,
157+ "query" : query
22158 }
23159
24-
25160 return state
0 commit comments