File size: 5,451 Bytes
cb92864
 
 
 
 
 
 
 
 
 
08fb91a
 
cb92864
 
08fb91a
 
cb92864
 
 
 
 
 
 
 
7f57ffc
cb92864
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
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()