🧭 LangGraph μ™„μ „ 정볡 ⑦ β€” LangGraph + UI 톡합 (Gradio, Streamlit)

okorionΒ·2025λ…„ 10μ›” 4일

β€œμ½”λ“œμ—μ„œ νλ¦„μœΌλ‘œ, νλ¦„μ—μ„œ μΈν„°λž™μ…˜μœΌλ‘œ.”
LangGraph κ·Έλž˜ν”„λ₯Ό μ‹œκ°ν™”ν•˜κ³  μ‹€μ‹œκ°„ μ‘°μž‘ κ°€λŠ₯ν•œ UI둜 μ—°κ²°ν•˜κΈ°.


🎯 1. λͺ©ν‘œ

μ§€κΈˆκΉŒμ§€μ˜ LangGraphλŠ” μ½˜μ†” μ€‘μ‹¬μœΌλ‘œ μ‹€ν–‰λ˜μ—ˆμ£ .
ν•˜μ§€λ§Œ μ‹€μ œ μ‘μš©μ—μ„œλŠ” μ‚¬μš©μž μž…λ ₯, μƒνƒœ ν‘œμ‹œ, κ·Έλž˜ν”„ μ‹œκ°ν™”, 디버깅 λ‘œκ·Έκ°€ ν•„μš”ν•©λ‹ˆλ‹€.

이번 νŽΈμ—μ„œλŠ” λ‹€μŒμ„ λͺ¨λ‘ λ‹€λ£Ήλ‹ˆλ‹€.

βœ… LangGraph + Gradio: κ·Έλž˜ν”„ 기반 μ±„νŒ… μΈν„°νŽ˜μ΄μŠ€
βœ… LangGraph + Streamlit: λ…Έλ“œ μƒνƒœ μ‹œκ°ν™” λŒ€μ‹œλ³΄λ“œ
βœ… μ‹€μ‹œκ°„ μƒνƒœ μ—…λ°μ΄νŠΈ (WebSocket / Callback)


🧩 2. κΈ°λ³Έ ꡬ쑰 κ°œμš”

πŸ“Š LangGraph + UI 톡합 ꡬ쑰:

각 λ…Έλ“œμ˜ μ‹€ν–‰ μƒνƒœλ₯Ό UI에 ν‘œμ‹œν•˜κ³ , μ‚¬μš©μžμ˜ μž…λ ₯(ν…μŠ€νŠΈΒ·λ²„νŠΌ λ“±)이 κ·Έλž˜ν”„ 싀행을 νŠΈλ¦¬κ±°ν•˜λŠ” κ΅¬μ‘°μž…λ‹ˆλ‹€.


βš™οΈ 3. Gradio 연동 μ˜ˆμ‹œ: λŒ€ν™”ν˜• κ·Έλž˜ν”„ μ•±

LangGraphλŠ” Gradio와 μ•„μ£Ό μ‰½κ²Œ ν†΅ν•©λ©λ‹ˆλ‹€.
LLM 호좜 κ²°κ³Όλ₯Ό μŠ€νŠΈλ¦¬λ°ν•˜κ±°λ‚˜, λ…Έλ“œ μ‹€ν–‰ κ²°κ³Όλ₯Ό μ‹€μ‹œκ°„ ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import gradio as gr
from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI

# μƒνƒœ μ •μ˜
class ChatState:
    messages: list[str]

# κ·Έλž˜ν”„ μ •μ˜
def chat_node(state: ChatState):
    llm = ChatOpenAI(model="gpt-4o-mini")
    user_msg = state.messages[-1]
    reply = llm.invoke(user_msg)
    state.messages.append(reply.content)
    return state

graph = StateGraph(ChatState)
graph.add_node("chat", chat_node)
graph.set_entry_point("chat")
graph.set_finish_point("chat")
app = graph.compile()

# Gradio μΈν„°νŽ˜μ΄μŠ€
def chat_fn(user_input, history):
    state = ChatState(messages=history + [user_input])
    result = app.invoke(state)
    return result.messages, result.messages

ui = gr.ChatInterface(
    fn=chat_fn,
    title="LangGraph Chatbot",
    description="LangGraph λ…Έλ“œ 기반 LLM λŒ€ν™” 흐름 μ˜ˆμ‹œ",
)

ui.launch()

βœ… μ‹€ν–‰ ν›„:

  • μ‚¬μš©μžμ˜ μž…λ ₯이 StateGraph에 μ „λ‹¬λ˜κ³ ,
  • LangGraphκ°€ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λ©°,
  • Gradio μ±„νŒ…μ°½μ— μ‹€μ‹œκ°„ κ²°κ³Όκ°€ λ°˜μ˜λ©λ‹ˆλ‹€.

πŸ” 4. μƒνƒœ 좔적 + μ‹œκ°ν™”

LangGraphλŠ” λ‚΄λΆ€ μƒνƒœλ₯Ό μ‹œκ°ν™”ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

graph.visualize("chat_flow.png")

λ˜λŠ” Gradio UI λ‚΄λΆ€μ—μ„œ 직접 μƒνƒœλ₯Ό ν‘œμ‹œν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€:

def debug_panel(state):
    return f"ν˜„μž¬ λ©”μ‹œμ§€ 수: {len(state.messages)}\n졜근 응닡: {state.messages[-1]}"

Gradioμ—μ„œ stateλ₯Ό 좜λ ₯ μ˜μ—­μœΌλ‘œ μ—°κ²°ν•˜λ©΄, λŒ€ν™”λΏ μ•„λ‹ˆλΌ λ‚΄λΆ€ 흐름을 ν•¨κ»˜ λͺ¨λ‹ˆν„°λ§ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


🧠 5. Streamlit 톡합 μ˜ˆμ‹œ: κ·Έλž˜ν”„ μƒνƒœ λŒ€μ‹œλ³΄λ“œ

Streamlit은 LangGraph μ‹€ν–‰ 둜그λ₯Ό μ‹€μ‹œκ°„ μ‹œκ°ν™”ν•˜κΈ°μ— μ ν•©ν•©λ‹ˆλ‹€.

import streamlit as st
from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI

# μƒνƒœ
class QAState:
    query: str
    answer: str

# λ…Έλ“œ
def answer_node(state: QAState):
    llm = ChatOpenAI(model="gpt-4o-mini")
    res = llm.invoke(f"Q: {state.query}\nA:")
    state.answer = res.content
    return state

graph = StateGraph(QAState)
graph.add_node("answer", answer_node)
graph.set_entry_point("answer")
graph.set_finish_point("answer")
app = graph.compile()

# Streamlit UI
st.title("LangGraph QA Dashboard")

query = st.text_input("μ§ˆλ¬Έμ„ μž…λ ₯ν•˜μ„Έμš”:")
if st.button("μ‹€ν–‰"):
    state = app.invoke({"query": query})
    st.subheader("LLM 응닡")
    st.write(state["answer"])
    st.image(graph.visualize("graph.png"))

βœ… μ‹€ν–‰ ν›„:

  • μž…λ ₯창에 질문 μž…λ ₯
  • LangGraph μ‹€ν–‰ κ²°κ³Ό μ¦‰μ‹œ ν‘œμ‹œ
  • graph.visualize()둜 κ·Έλž˜ν”„ 이미지 ν‘œμ‹œ

🧰 6. WebSocket 기반 μ‹€μ‹œκ°„ 이벀트 처리 (κ³ κΈ‰)

LangGraph μ‹€ν–‰ 쀑간에 각 λ…Έλ“œμ˜ μƒνƒœλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ λΈŒλ‘œλ“œμΊμŠ€νŠΈν•˜λ €λ©΄ CallbackManagerλ‚˜ WebSocket을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

from langgraph.callbacks.base import CallbackManager

def on_node_start(node_name, state):
    print(f"[Node Start] {node_name}")

def on_node_end(node_name, state):
    print(f"[Node End] {node_name}")

manager = CallbackManager(
    on_node_start=on_node_start,
    on_node_end=on_node_end
)

app = graph.compile(callbacks=manager)

이 데이터λ₯Ό WebSocket으둜 μŠ€νŠΈλ¦¬λ°ν•˜λ©΄ μ‹€μ‹œκ°„ κ·Έλž˜ν”„ β€œλ…Έλ“œ μ‹€ν–‰ λ‘œκ·Έβ€ UIλ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.


🎨 7. μ‹€μ „ UX 섀계 팁

λͺ©μ κ΅¬ν˜„ 방식비고
λŒ€ν™”ν˜• 챗봇gr.ChatInterface직관적 μΈν„°λž™μ…˜
λŒ€μ‹œλ³΄λ“œν˜•StreamlitμƒνƒœΒ·λ…Έλ“œ μ‹œκ°ν™”
μ‹€μ‹œκ°„ 좔적CallbackManager / WS디버깅 및 운영용
λ…Έλ“œλ³„ 둜그각 λ…Έλ“œ λ‚΄ print λ˜λŠ” state.log.append()μƒνƒœ 기반 λ‘œκΉ…
Mermaid μ‹œκ°ν™”graph.visualize() or 직접 Mermaid μ½”λ“œ μƒμ„±λ¬Έμ„œ μžλ™ν™” κ°€λŠ₯

🧭 8. λ‹€μŒ 회차 예고

πŸ‘‰ 8편: μ‹€μ „ μ•„ν‚€ν…μ²˜ 섀계 & Best Practice
λ§ˆμ§€λ§‰ νŽΈμ—μ„œλŠ” μ§€κΈˆκΉŒμ§€ 배운 λ‚΄μš©μ„ μ’…ν•©ν•˜μ—¬ λŒ€κ·œλͺ¨ κ·Έλž˜ν”„ 섀계, μ„±λŠ₯ μ΅œμ ν™”, 였λ₯˜ 처리, 배포 μ „λž΅μ„ λͺ¨λ‘ λ‹€λ£Ήλ‹ˆλ‹€.


πŸŽ“ 9. 더 깊이 배우기 μœ„ν•œ κ³ κΈ‰ ν™•μž₯ ν•™μŠ΅ κ°€μ΄λ“œ

μ£Όμ œν•™μŠ΅ μ΄μœ μΆ”μ²œ ν•™μŠ΅ λ°©ν–₯
Event-driven UIμ‹€μ‹œκ°„ κ·Έλž˜ν”„ μ‹€ν–‰ μƒνƒœ 반영WebSocket, async callback
Graph Visualization Librariesκ·Έλž˜ν”„λ₯Ό μΈν„°λž™ν‹°λΈŒν•˜κ²Œ μ‹œκ°ν™”D3.js, Cytoscape.js, NetworkX
LangGraph Callback Systemλ…Έλ“œ μ‹€ν–‰ μƒνƒœ λͺ¨λ‹ˆν„°λ§CallbackManager, custom logger
LangServe IntegrationUI와 κ·Έλž˜ν”„ λ°±μ—”λ“œ 뢄리LangServe REST endpoint
Frontend-Graph Syncμƒνƒœ 객체λ₯Ό UI와 μ–‘λ°©ν–₯ 동기화React + FastAPI μ‘°ν•©
User Feedback Loop (HITL UI)μ‚¬μš©μž ν”Όλ“œλ°±μ„ κ·Έλž˜ν”„μ— 직접 λ°˜μ˜ν‰κ°€μš© λ²„νŠΌ, μˆ˜μ • ν”Όλ“œλ°± 이벀트

πŸ“š 핡심 μš”μ•½

  • LangGraphλŠ” Gradio/Streamlitκ³Ό κ²°ν•©ν•˜μ—¬ μ‹œκ°μ  μ›Œν¬ν”Œλ‘œμš° μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.
  • GradioλŠ” μ‹€μ‹œκ°„ λŒ€ν™”μš©, Streamlit은 λΆ„μ„Β·λ””λ²„κΉ…μš©μœΌλ‘œ μ ν•©ν•˜λ‹€.
  • Callbackκ³Ό WebSocket을 톡해 κ·Έλž˜ν”„μ˜ μ‹€μ‹œκ°„ μƒνƒœ 좔적도 κ°€λŠ₯ν•˜λ‹€.

πŸ’‘ μ½”λ“œλ‘œ μ„€κ³„ν•œ κ·Έλž˜ν”„κ°€ 이제 λˆˆμ•žμ˜ β€œνλ¦„β€μœΌλ‘œ 보이기 μ‹œμž‘ν•œλ‹€.

profile
okorion's Tech Study Blog.

0개의 λŒ“κΈ€