dev-yuje commited on
Commit
622f700
·
1 Parent(s): 6c92d06

fix: RAG 검색 진행과정 표시 구현 및 예시 질문 응답 누락 해결 (골드 시나리오 100% 통과)

Browse files
Files changed (3) hide show
  1. app.py +18 -20
  2. src/retrieval/finRetrieval.py +18 -8
  3. tests/smoke_test_rag.py +1 -1
app.py CHANGED
@@ -94,7 +94,7 @@ chat_graph = builder.compile()
94
  # ──────────────────────────────────────────
95
 
96
 
97
- def chat(message: str, history: list) -> str:
98
  """Gradio ChatInterface가 호출하는 함수.
99
 
100
  Args:
@@ -103,10 +103,11 @@ def chat(message: str, history: list) -> str:
103
  [{"role": "user"/"assistant", "content": "..."}] 형식
104
 
105
  Returns:
106
- str: 챗봇 답변
107
  """
108
  if not message.strip():
109
- return "질문을 입력해 주세요."
 
110
 
111
  # Gradio history → LangGraph state 형식으로 변환
112
  state: ChatState = {
@@ -116,8 +117,17 @@ def chat(message: str, history: list) -> str:
116
  "answer": "",
117
  }
118
 
119
- result = chat_graph.invoke(state)
120
- return result["answer"]
 
 
 
 
 
 
 
 
 
121
 
122
 
123
  def get_db_stats() -> Dict[str, Any]:
@@ -516,10 +526,10 @@ interface_kwargs = {
516
  "title": "FinNode — AI 기업 트렌드 분석 챗봇",
517
  "description": "> 최신 AI 뉴스를 기반으로 구축된 지식 그래프(GraphRAG)에서 답변합니다.",
518
  "examples": [
519
- "삼성전자의 최근 AI 기술 트렌드는?",
520
- "카카오가 개발 중인 AI 서비스 목록을 알려줘",
521
  "어떤 기업이 LLM 기술을 개발하나요?",
522
- "최근 AI 관련 기사를 요약해줘",
 
 
523
  ],
524
  "cache_examples": False,
525
  }
@@ -565,18 +575,6 @@ with gr.Blocks(**blocks_kwargs) as demo:
565
  stats_html = build_stats_html(stats_data)
566
  gr.HTML(stats_html)
567
 
568
- # 사이드바 하단 도움말/설정 메뉴
569
- gr.HTML("""
570
- <div style="margin-top: 15px; padding: 15px 20px; background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; font-size: 14px; color: #475569; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.02);">
571
- <div style="cursor: pointer; display: flex; align-items: center; gap: 8px; transition: color 0.2s;" onmouseover="this.style.color='#4f46e5'" onmouseout="this.style.color='#475569'">
572
- <span style="font-size:16px;">❔</span> 도움말
573
- </div>
574
- <div style="cursor: pointer; display: flex; align-items: center; gap: 8px; transition: color 0.2s;" onmouseover="this.style.color='#4f46e5'" onmouseout="this.style.color='#475569'">
575
- <span style="font-size:16px;">👤</span> 사용자 설정
576
- </div>
577
- </div>
578
- """)
579
-
580
  # 3. 오른쪽 컬럼: 메인 챗봇 에어리어
581
  with gr.Column(scale=3):
582
  # 메인 타이틀 (챗봇 영역 상단 중앙)
 
94
  # ──────────────────────────────────────────
95
 
96
 
97
+ def chat(message: str, history: list):
98
  """Gradio ChatInterface가 호출하는 함수.
99
 
100
  Args:
 
103
  [{"role": "user"/"assistant", "content": "..."}] 형식
104
 
105
  Returns:
106
+ Generator: 챗봇 답변 (실시간 상태 표시 포함)
107
  """
108
  if not message.strip():
109
+ yield "질문을 입력해 주세요."
110
+ return
111
 
112
  # Gradio history → LangGraph state 형식으로 변환
113
  state: ChatState = {
 
117
  "answer": "",
118
  }
119
 
120
+ yield "🔍 실시간 지식 그래프에서 관련 뉴스를 검색하는 중입니다..."
121
+
122
+ try:
123
+ # LangGraph의 stream을 사용하여 각 노드 실행 시점마다 이벤트를 받음
124
+ for event in chat_graph.stream(state):
125
+ if "retrieve" in event:
126
+ yield "💡 검색 완료! 분석 결과를 바탕으로 최종 답변을 생성하는 중입니다..."
127
+ elif "generate" in event:
128
+ yield event["generate"]["answer"]
129
+ except Exception as e:
130
+ yield f"⚠️ 챗봇 처리 중 오류가 발생했습니다: {str(e)}"
131
 
132
 
133
  def get_db_stats() -> Dict[str, Any]:
 
526
  "title": "FinNode — AI 기업 트렌드 분석 챗봇",
527
  "description": "> 최신 AI 뉴스를 기반으로 구축된 지식 그래프(GraphRAG)에서 답변합니다.",
528
  "examples": [
 
 
529
  "어떤 기업이 LLM 기술을 개발하나요?",
530
+ "KT나 SKT 등 통신사들의 AI 비서 서비 LLM 전략",
531
+ "최근 1주일간 가장 이슈가 된 AI 분야 뉴스 종합 브리핑",
532
+ "국내 주요 기업들의 최신 생성형 AI 서비스 출시 동향은?",
533
  ],
534
  "cache_examples": False,
535
  }
 
575
  stats_html = build_stats_html(stats_data)
576
  gr.HTML(stats_html)
577
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  # 3. 오른쪽 컬럼: 메인 챗봇 에어리어
579
  with gr.Column(scale=3):
580
  # 메인 타이틀 (챗봇 영역 상단 중앙)
src/retrieval/finRetrieval.py CHANGED
@@ -10,8 +10,13 @@ app.py에서 import하여 Gradio 챗봇과 연동합니다.
10
  print(response.answer)
11
  """
12
 
 
13
  import os
14
 
 
 
 
 
15
  import dotenv
16
  import neo4j
17
  from neo4j_graphrag.embeddings.openai import OpenAIEmbeddings
@@ -94,31 +99,36 @@ _examples = [
94
  """USER INPUT: 카카오의 AI 서비스 목록을 알려주세요
95
  CYPHER QUERY:
96
  MATCH (c:AICompany {name:"카카오"})-[:DEVELOPS]->(s:AIService)
97
- RETURN s.name, s.description""",
 
98
  """USER INPUT: 삼성전자가 개발 중인 AI 기술은?
99
  CYPHER QUERY:
100
  MATCH (c:AICompany {name:"삼성전자"})-[:DEVELOPS]->(t:AITechnology)
101
- RETURN t.name, t.description""",
 
102
  """USER INPUT: 어떤 기업이 LLM 기술을 개발하나요?
103
  CYPHER QUERY:
104
  MATCH (c:AICompany)-[:DEVELOPS]->(t:AITechnology)
105
  WHERE t.name CONTAINS "언어모델" OR t.name CONTAINS "LLM"
106
- RETURN c.name, t.name""",
 
107
  """USER INPUT: 금융이나 핀테크 분야에 기술을 적용하고 있는 기업들은 어디야?
108
  CYPHER QUERY:
109
  MATCH (c:AICompany)-[:DEVELOPS]->(t)-[:USED_IN]->(f:AIField)
110
  WHERE f.name CONTAINS "금융" OR f.name CONTAINS "핀테크"
111
- RETURN DISTINCT c.name, t.name, f.name""",
 
112
  """USER INPUT: 금융AI 분야에 가장 적극적인 기업 TOP 3와 대표 서비스
113
  CYPHER QUERY:
114
  MATCH (c:AICompany)-[:DEVELOPS]->(s)-[:USED_IN]->(f:AIField)
115
  WHERE f.name CONTAINS "금융" OR f.name CONTAINS "핀테크"
116
- RETURN DISTINCT c.name, s.name, f.name
 
117
  LIMIT 3""",
118
  """USER INPUT: 최근 AI 관련 뉴스 기사를 요약해줘
119
  CYPHER QUERY:
120
  MATCH (a:Article)-[:HAS_CHUNK]->(c:Content)
121
- RETURN a.title, a.url, a.published_date, c.chunk
122
  ORDER BY a.published_date DESC
123
  LIMIT 3""",
124
  ]
@@ -166,8 +176,8 @@ _prompt_template = CustomRagTemplate(
166
 
167
  ⚠️ [엄격한 주의사항]
168
  1. 컨텍스트에 없는 기업, 서비스, 기술은 절대 언급하지 마세요. (해외 기업도 컨텍스트에 있으면 요약 가능합니다)
169
- 2. 질문에 해당하는 정보가 컨텍스트에 없다면 지어내지 말고, "현재 수집된 최신 뉴스 데이터에는 관련 정보가 없습니다"라고 정직하게 답변하세요.
170
- 3. 근거로 URL은 오직 컨텍스트에 포함된 실제 기사 URL 사용며, 'example.com' 같은 가짜 링크는 절대 생성하지 마세요.
171
  4. 취업 지원 목적의 기업 분석은 구체적으로 작성하고, "최근 뉴스 기사 요약" 등의 일반 트렌드 질문은 핵심 내용을 잘 정리하여 브리핑해주세요.
172
 
173
  질문: {query_text}
 
10
  print(response.answer)
11
  """
12
 
13
+ import logging
14
  import os
15
 
16
+ # Neo4j DBMS server warning (Deprecated vector queryNodes 등) 로깅 차단
17
+ logging.getLogger("neo4j").setLevel(logging.ERROR)
18
+ logging.getLogger("neo4j.notifications").setLevel(logging.ERROR)
19
+
20
  import dotenv
21
  import neo4j
22
  from neo4j_graphrag.embeddings.openai import OpenAIEmbeddings
 
99
  """USER INPUT: 카카오의 AI 서비스 목록을 알려주세요
100
  CYPHER QUERY:
101
  MATCH (c:AICompany {name:"카카오"})-[:DEVELOPS]->(s:AIService)
102
+ OPTIONAL MATCH (a:Article)-[:MENTIONS]->(s)
103
+ RETURN s.name AS name, s.description AS description, a.title AS article_title, a.url AS article_url""",
104
  """USER INPUT: 삼성전자가 개발 중인 AI 기술은?
105
  CYPHER QUERY:
106
  MATCH (c:AICompany {name:"삼성전자"})-[:DEVELOPS]->(t:AITechnology)
107
+ OPTIONAL MATCH (a:Article)-[:MENTIONS]->(t)
108
+ RETURN t.name AS name, t.description AS description, a.title AS article_title, a.url AS article_url""",
109
  """USER INPUT: 어떤 기업이 LLM 기술을 개발하나요?
110
  CYPHER QUERY:
111
  MATCH (c:AICompany)-[:DEVELOPS]->(t:AITechnology)
112
  WHERE t.name CONTAINS "언어모델" OR t.name CONTAINS "LLM"
113
+ OPTIONAL MATCH (a:Article)-[:MENTIONS]->(t)
114
+ RETURN c.name AS company_name, t.name AS tech_name, a.title AS article_title, a.url AS article_url""",
115
  """USER INPUT: 금융이나 핀테크 분야에 기술을 적용하고 있는 기업들은 어디야?
116
  CYPHER QUERY:
117
  MATCH (c:AICompany)-[:DEVELOPS]->(t)-[:USED_IN]->(f:AIField)
118
  WHERE f.name CONTAINS "금융" OR f.name CONTAINS "핀테크"
119
+ OPTIONAL MATCH (a:Article)-[:MENTIONS]->(t)
120
+ RETURN DISTINCT c.name AS company_name, t.name AS tech_name, f.name AS field_name, a.title AS article_title, a.url AS article_url""",
121
  """USER INPUT: 금융AI 분야에 가장 적극적인 기업 TOP 3와 대표 서비스
122
  CYPHER QUERY:
123
  MATCH (c:AICompany)-[:DEVELOPS]->(s)-[:USED_IN]->(f:AIField)
124
  WHERE f.name CONTAINS "금융" OR f.name CONTAINS "핀테크"
125
+ OPTIONAL MATCH (a:Article)-[:MENTIONS]->(s)
126
+ RETURN DISTINCT c.name AS company_name, s.name AS service_name, f.name AS field_name, a.title AS article_title, a.url AS article_url
127
  LIMIT 3""",
128
  """USER INPUT: 최근 AI 관련 뉴스 기사를 요약해줘
129
  CYPHER QUERY:
130
  MATCH (a:Article)-[:HAS_CHUNK]->(c:Content)
131
+ RETURN a.title AS title, a.url AS url, a.published_date AS published_date, c.chunk AS chunk
132
  ORDER BY a.published_date DESC
133
  LIMIT 3""",
134
  ]
 
176
 
177
  ⚠️ [엄격한 주의사항]
178
  1. 컨텍스트에 없는 기업, 서비스, 기술은 절대 언급하지 마세요. (해외 기업도 컨텍스트에 있으면 요약 가능합니다)
179
+ 2. 질문에 해당하는 정보가 컨텍스트에 없다면 지어내지 말고, "현재 수집된 최신 뉴스 데이터에는 관련 정보가 없습니다"라고 정직하게 답변하세요. 단, 컨텍스트에 휴머노이드 로봇(아틀라스 등), 반도체(HBM3E 등), 인프라 신기술 등 AI 연관 기술이 기술되어 있다면 이를 AI 관련 소식으로 간주하여 성실히 요약해 주세요.
180
+ 3. 답변반드시 관련 기사 제목과 URL 함께 표기 출처를 명확히 밝혀주세요 (예: [기사 제목](기사 URL)).
181
  4. 취업 지원 목적의 기업 분석은 구체적으로 작성하고, "최근 뉴스 기사 요약" 등의 일반 트렌드 질문은 핵심 내용을 잘 정리하여 브리핑해주세요.
182
 
183
  질문: {query_text}
tests/smoke_test_rag.py CHANGED
@@ -12,10 +12,10 @@ smoke_test_rag.py — GraphRAG 3대 시나리오 현장 검증 스크립트
12
  python3 tests/smoke_test_rag.py
13
  """
14
 
 
15
  import os
16
  import sys
17
  import time
18
- import io
19
 
20
  # Windows 환경에서 유니코드 이모지 출력 시 UnicodeEncodeError(cp949) 방지를 위한 stdout 인코딩 재설정
21
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
 
12
  python3 tests/smoke_test_rag.py
13
  """
14
 
15
+ import io
16
  import os
17
  import sys
18
  import time
 
19
 
20
  # Windows 환경에서 유니코드 이모지 출력 시 UnicodeEncodeError(cp949) 방지를 위한 stdout 인코딩 재설정
21
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')