FinGraph / app.py
dev-yuje's picture
fix: remove type='messages' from ChatInterface to resolve Gradio format mismatch
ea86e27
raw
history blame
5.45 kB
"""
app.py β€” FinNode GraphRAG 챗봇
================================
Hugging Face Spaces 배포 μ§„μž…μ .
Gradio ChatInterface + LangGraph 기반 λŒ€ν™” 흐름 μ œμ–΄.
μ‹€ν–‰:
python app.py
"""
from typing import List, TypedDict
import dotenv
import gradio as gr
from langgraph.graph import END, StateGraph
from src.retrieval.finRetrieval import graphrag
dotenv.load_dotenv()
# ──────────────────────────────────────────
# 1. LangGraph 챗봇 State μ •μ˜
# ──────────────────────────────────────────
class ChatState(TypedDict):
question: str # μ‚¬μš©μž 질문
history: List[dict] # λŒ€ν™” νžˆμŠ€ν† λ¦¬ [{"role": "user"/"assistant", "content": "..."}]
context: str # GraphRAG 검색 κ²°κ³Ό
answer: str # μ΅œμ’… λ‹΅λ³€
# ──────────────────────────────────────────
# 2. LangGraph λ…Έλ“œ μ •μ˜
# ──────────────────────────────────────────
def retrieve_node(state: ChatState) -> ChatState:
"""Node 1: GraphRAG둜 κ΄€λ ¨ μ»¨ν…μŠ€νŠΈ 검색"""
try:
result = graphrag.search(query_text=state["question"])
context = result.answer # GraphRAGκ°€ 이미 닡변을 μ™„μ„±ν•˜λ―€λ‘œ λ°”λ‘œ μ‚¬μš©
except Exception as e:
context = f"[검색 였λ₯˜: {e}]"
return {**state, "context": context}
def generate_node(state: ChatState) -> ChatState:
"""Node 2: λŒ€ν™” νžˆμŠ€ν† λ¦¬λ₯Ό κ³ λ €ν•˜μ—¬ μ΅œμ’… λ‹΅λ³€ 생성
GraphRAGκ°€ 이미 검색 + 생성을 μ²˜λ¦¬ν•˜λ―€λ‘œ,
μ—¬κΈ°μ„œλŠ” νžˆμŠ€ν† λ¦¬ 기반 ν›„μ²˜λ¦¬λ‚˜ μΆ”κ°€ ν¬λ§·νŒ…λ§Œ μˆ˜ν–‰ν•©λ‹ˆλ‹€.
"""
# GraphRAG κ²°κ³Όλ₯Ό λ°”λ‘œ λ‹΅λ³€μœΌλ‘œ μ‚¬μš©
# (νžˆμŠ€ν† λ¦¬ 기반 후속 질문 μ²˜λ¦¬κ°€ ν•„μš”ν•˜λ©΄ 이 λ…Έλ“œλ₯Ό ν™•μž₯ν•˜μ„Έμš”)
answer = state["context"] if state["context"] else "κ΄€λ ¨ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
return {**state, "answer": answer}
# ──────────────────────────────────────────
# 3. LangGraph μ›Œν¬ν”Œλ‘œμš° 컴파일
# ──────────────────────────────────────────
builder = StateGraph(ChatState)
builder.add_node("retrieve", retrieve_node)
builder.add_node("generate", generate_node)
builder.set_entry_point("retrieve")
builder.add_edge("retrieve", "generate")
builder.add_edge("generate", END)
chat_graph = builder.compile()
# ──────────────────────────────────────────
# 4. Gradio 연동 ν•¨μˆ˜
# ──────────────────────────────────────────
def chat(message: str, history: list) -> str:
"""Gradio ChatInterfaceκ°€ ν˜ΈμΆœν•˜λŠ” ν•¨μˆ˜.
Args:
message: μ‚¬μš©μž μž…λ ₯ λ©”μ‹œμ§€
history: Gradioκ°€ κ΄€λ¦¬ν•˜λŠ” λŒ€ν™” νžˆμŠ€ν† λ¦¬
[{"role": "user"/"assistant", "content": "..."}] ν˜•μ‹
Returns:
str: 챗봇 λ‹΅λ³€
"""
if not message.strip():
return "μ§ˆλ¬Έμ„ μž…λ ₯ν•΄ μ£Όμ„Έμš”."
# Gradio history β†’ LangGraph state ν˜•μ‹μœΌλ‘œ λ³€ν™˜
state: ChatState = {
"question": message,
"history": history,
"context": "",
"answer": "",
}
result = chat_graph.invoke(state)
return result["answer"]
# ──────────────────────────────────────────
# 5. Gradio UI ꡬ성
# ──────────────────────────────────────────
with gr.Blocks(
title="FinNode β€” AI κΈ°μ—… νŠΈλ Œλ“œ 뢄석 챗봇",
theme=gr.themes.Soft(primary_hue="indigo"),
) as demo:
gr.Markdown(
"""
# πŸ”— FinNode β€” AI κΈ°μ—… νŠΈλ Œλ“œ 뢄석 챗봇
> μ΅œμ‹  AI λ‰΄μŠ€λ₯Ό 기반으둜 κ΅¬μΆ•λœ 지식 κ·Έλž˜ν”„(GraphRAG)μ—μ„œ λ‹΅λ³€ν•©λ‹ˆλ‹€.
**μ˜ˆμ‹œ 질문**
- μ‚Όμ„±μ „μžμ˜ 졜근 AI 기술 νŠΈλ Œλ“œλŠ”?
- μΉ΄μΉ΄μ˜€κ°€ 개발 쀑인 AI μ„œλΉ„μŠ€ λͺ©λ‘μ„ μ•Œλ €μ€˜
- μ–΄λ–€ 기업이 LLM κΈ°μˆ μ„ κ°œλ°œν•˜λ‚˜μš”?
- 졜근 AI κ΄€λ ¨ λ‰΄μŠ€ 기사λ₯Ό μš”μ•½ν•΄μ€˜
"""
)
chatbot = gr.ChatInterface(
fn=chat,
chatbot=gr.Chatbot(
height=500,
placeholder="μ§ˆλ¬Έμ„ μž…λ ₯ν•˜λ©΄ 지식 κ·Έλž˜ν”„μ—μ„œ 닡변을 μ°Ύμ•„λ“œλ¦½λ‹ˆλ‹€.",
),
textbox=gr.Textbox(
placeholder="예: λ„€μ΄λ²„μ˜ AI 기술 νŠΈλ Œλ“œλŠ” λ¬΄μ—‡μΈκ°€μš”?",
container=False,
scale=7,
),
examples=[
"μ‚Όμ„±μ „μžμ˜ 졜근 AI 기술 νŠΈλ Œλ“œλŠ”?",
"μΉ΄μΉ΄μ˜€κ°€ 개발 쀑인 AI μ„œλΉ„μŠ€ λͺ©λ‘μ„ μ•Œλ €μ€˜",
"μ–΄λ–€ 기업이 LLM κΈ°μˆ μ„ κ°œλ°œν•˜λ‚˜μš”?",
"졜근 AI κ΄€λ ¨ λ‰΄μŠ€ 기사λ₯Ό μš”μ•½ν•΄μ€˜",
],
retry_btn=None,
undo_btn="↩️ 되돌리기",
clear_btn="πŸ—‘οΈ λŒ€ν™” μ΄ˆκΈ°ν™”",
)
if __name__ == "__main__":
demo.launch()