fix: RAG 검색 진행과정 표시 구현 및 예시 질문 응답 누락 해결 (골드 시나리오 100% 통과)
Browse files- app.py +18 -20
- src/retrieval/finRetrieval.py +18 -8
- 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)
|
| 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 |
-
|
| 107 |
"""
|
| 108 |
if not message.strip():
|
| 109 |
-
|
|
|
|
| 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 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 98 |
"""USER INPUT: 삼성전자가 개발 중인 AI 기술은?
|
| 99 |
CYPHER QUERY:
|
| 100 |
MATCH (c:AICompany {name:"삼성전자"})-[:DEVELOPS]->(t:AITechnology)
|
| 101 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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.
|
| 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')
|